@herdctl/core 0.0.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +219 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-final.json +51 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +251 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/config/index.html +191 -0
- package/coverage/src/config/index.ts.html +442 -0
- package/coverage/src/config/interpolate.ts.html +652 -0
- package/coverage/src/config/loader.ts.html +1501 -0
- package/coverage/src/config/merge.ts.html +823 -0
- package/coverage/src/config/parser.ts.html +1213 -0
- package/coverage/src/config/schema.ts.html +1123 -0
- package/coverage/src/fleet-manager/errors.ts.html +2326 -0
- package/coverage/src/fleet-manager/event-types.ts.html +1219 -0
- package/coverage/src/fleet-manager/fleet-manager.ts.html +7030 -0
- package/coverage/src/fleet-manager/index.html +206 -0
- package/coverage/src/fleet-manager/index.ts.html +469 -0
- package/coverage/src/fleet-manager/job-manager.ts.html +2074 -0
- package/coverage/src/fleet-manager/job-queue.ts.html +2479 -0
- package/coverage/src/fleet-manager/types.ts.html +2602 -0
- package/coverage/src/index.html +116 -0
- package/coverage/src/index.ts.html +181 -0
- package/coverage/src/runner/errors.ts.html +1006 -0
- package/coverage/src/runner/index.html +191 -0
- package/coverage/src/runner/index.ts.html +256 -0
- package/coverage/src/runner/job-executor.ts.html +1429 -0
- package/coverage/src/runner/message-processor.ts.html +1150 -0
- package/coverage/src/runner/sdk-adapter.ts.html +658 -0
- package/coverage/src/runner/types.ts.html +559 -0
- package/coverage/src/scheduler/errors.ts.html +388 -0
- package/coverage/src/scheduler/index.html +206 -0
- package/coverage/src/scheduler/index.ts.html +244 -0
- package/coverage/src/scheduler/interval.ts.html +652 -0
- package/coverage/src/scheduler/schedule-runner.ts.html +1411 -0
- package/coverage/src/scheduler/schedule-state.ts.html +718 -0
- package/coverage/src/scheduler/scheduler.ts.html +1795 -0
- package/coverage/src/scheduler/types.ts.html +733 -0
- package/coverage/src/state/directory.ts.html +736 -0
- package/coverage/src/state/errors.ts.html +376 -0
- package/coverage/src/state/fleet-state.ts.html +937 -0
- package/coverage/src/state/index.html +221 -0
- package/coverage/src/state/index.ts.html +322 -0
- package/coverage/src/state/job-metadata.ts.html +1420 -0
- package/coverage/src/state/job-output.ts.html +1033 -0
- package/coverage/src/state/schemas/fleet-state.ts.html +445 -0
- package/coverage/src/state/schemas/index.html +176 -0
- package/coverage/src/state/schemas/index.ts.html +286 -0
- package/coverage/src/state/schemas/job-metadata.ts.html +628 -0
- package/coverage/src/state/schemas/job-output.ts.html +616 -0
- package/coverage/src/state/schemas/session-info.ts.html +361 -0
- package/coverage/src/state/session.ts.html +844 -0
- package/coverage/src/state/types.ts.html +262 -0
- package/coverage/src/state/utils/atomic.ts.html +748 -0
- package/coverage/src/state/utils/index.html +146 -0
- package/coverage/src/state/utils/index.ts.html +103 -0
- package/coverage/src/state/utils/reads.ts.html +1621 -0
- package/coverage/src/work-sources/adapters/github.ts.html +3583 -0
- package/coverage/src/work-sources/adapters/index.html +131 -0
- package/coverage/src/work-sources/adapters/index.ts.html +277 -0
- package/coverage/src/work-sources/errors.ts.html +298 -0
- package/coverage/src/work-sources/index.html +176 -0
- package/coverage/src/work-sources/index.ts.html +529 -0
- package/coverage/src/work-sources/manager.ts.html +1324 -0
- package/coverage/src/work-sources/registry.ts.html +619 -0
- package/coverage/src/work-sources/types.ts.html +568 -0
- package/dist/config/__tests__/agent.test.d.ts +2 -0
- package/dist/config/__tests__/agent.test.d.ts.map +1 -0
- package/dist/config/__tests__/agent.test.js +752 -0
- package/dist/config/__tests__/agent.test.js.map +1 -0
- package/dist/config/__tests__/interpolate.test.d.ts +2 -0
- package/dist/config/__tests__/interpolate.test.d.ts.map +1 -0
- package/dist/config/__tests__/interpolate.test.js +509 -0
- package/dist/config/__tests__/interpolate.test.js.map +1 -0
- package/dist/config/__tests__/loader.test.d.ts +2 -0
- package/dist/config/__tests__/loader.test.d.ts.map +1 -0
- package/dist/config/__tests__/loader.test.js +631 -0
- package/dist/config/__tests__/loader.test.js.map +1 -0
- package/dist/config/__tests__/merge.test.d.ts +2 -0
- package/dist/config/__tests__/merge.test.d.ts.map +1 -0
- package/dist/config/__tests__/merge.test.js +672 -0
- package/dist/config/__tests__/merge.test.js.map +1 -0
- package/dist/config/__tests__/parser.test.d.ts +2 -0
- package/dist/config/__tests__/parser.test.d.ts.map +1 -0
- package/dist/config/__tests__/parser.test.js +476 -0
- package/dist/config/__tests__/parser.test.js.map +1 -0
- package/dist/config/__tests__/schema.test.d.ts +2 -0
- package/dist/config/__tests__/schema.test.d.ts.map +1 -0
- package/dist/config/__tests__/schema.test.js +776 -0
- package/dist/config/__tests__/schema.test.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +26 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/interpolate.d.ts +76 -0
- package/dist/config/interpolate.d.ts.map +1 -0
- package/dist/config/interpolate.js +143 -0
- package/dist/config/interpolate.js.map +1 -0
- package/dist/config/loader.d.ts +147 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +336 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/merge.d.ts +84 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/merge.js +138 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/config/parser.d.ts +143 -0
- package/dist/config/parser.d.ts.map +1 -0
- package/dist/config/parser.js +316 -0
- package/dist/config/parser.js.map +1 -0
- package/dist/config/schema.d.ts +1906 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +268 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/fleet-manager/__tests__/coverage.test.d.ts +13 -0
- package/dist/fleet-manager/__tests__/coverage.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/coverage.test.js +2282 -0
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/errors.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/errors.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/errors.test.js +557 -0
- package/dist/fleet-manager/__tests__/errors.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/event-helpers.test.js +368 -0
- package/dist/fleet-manager/__tests__/event-helpers.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/integration.test.d.ts +11 -0
- package/dist/fleet-manager/__tests__/integration.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/integration.test.js +949 -0
- package/dist/fleet-manager/__tests__/integration.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/job-control.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/job-control.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/job-control.test.js +215 -0
- package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/job-manager.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/job-manager.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/job-manager.test.js +659 -0
- package/dist/fleet-manager/__tests__/job-manager.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/job-queue.test.d.ts +5 -0
- package/dist/fleet-manager/__tests__/job-queue.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/job-queue.test.js +315 -0
- package/dist/fleet-manager/__tests__/job-queue.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/reload.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/reload.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/reload.test.js +609 -0
- package/dist/fleet-manager/__tests__/reload.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/status-queries.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/status-queries.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/status-queries.test.js +488 -0
- package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/trigger.test.d.ts +7 -0
- package/dist/fleet-manager/__tests__/trigger.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/trigger.test.js +471 -0
- package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +407 -0
- package/dist/fleet-manager/errors.d.ts.map +1 -0
- package/dist/fleet-manager/errors.js +569 -0
- package/dist/fleet-manager/errors.js.map +1 -0
- package/dist/fleet-manager/event-types.d.ts +302 -0
- package/dist/fleet-manager/event-types.d.ts.map +1 -0
- package/dist/fleet-manager/event-types.js +9 -0
- package/dist/fleet-manager/event-types.js.map +1 -0
- package/dist/fleet-manager/fleet-manager.d.ts +699 -0
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -0
- package/dist/fleet-manager/fleet-manager.js +1906 -0
- package/dist/fleet-manager/fleet-manager.js.map +1 -0
- package/dist/fleet-manager/index.d.ts +17 -0
- package/dist/fleet-manager/index.d.ts.map +1 -0
- package/dist/fleet-manager/index.js +29 -0
- package/dist/fleet-manager/index.js.map +1 -0
- package/dist/fleet-manager/job-manager.d.ts +271 -0
- package/dist/fleet-manager/job-manager.d.ts.map +1 -0
- package/dist/fleet-manager/job-manager.js +443 -0
- package/dist/fleet-manager/job-manager.js.map +1 -0
- package/dist/fleet-manager/job-queue.d.ts +422 -0
- package/dist/fleet-manager/job-queue.d.ts.map +1 -0
- package/dist/fleet-manager/job-queue.js +448 -0
- package/dist/fleet-manager/job-queue.js.map +1 -0
- package/dist/fleet-manager/types.d.ts +680 -0
- package/dist/fleet-manager/types.d.ts.map +1 -0
- package/dist/fleet-manager/types.js +8 -0
- package/dist/fleet-manager/types.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/runner/__tests__/errors.test.d.ts +2 -0
- package/dist/runner/__tests__/errors.test.d.ts.map +1 -0
- package/dist/runner/__tests__/errors.test.js +264 -0
- package/dist/runner/__tests__/errors.test.js.map +1 -0
- package/dist/runner/__tests__/job-executor.test.d.ts +2 -0
- package/dist/runner/__tests__/job-executor.test.d.ts.map +1 -0
- package/dist/runner/__tests__/job-executor.test.js +1345 -0
- package/dist/runner/__tests__/job-executor.test.js.map +1 -0
- package/dist/runner/__tests__/message-processor.test.d.ts +2 -0
- package/dist/runner/__tests__/message-processor.test.d.ts.map +1 -0
- package/dist/runner/__tests__/message-processor.test.js +768 -0
- package/dist/runner/__tests__/message-processor.test.js.map +1 -0
- package/dist/runner/__tests__/sdk-adapter.test.d.ts +2 -0
- package/dist/runner/__tests__/sdk-adapter.test.d.ts.map +1 -0
- package/dist/runner/__tests__/sdk-adapter.test.js +554 -0
- package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -0
- package/dist/runner/errors.d.ts +121 -0
- package/dist/runner/errors.d.ts.map +1 -0
- package/dist/runner/errors.js +212 -0
- package/dist/runner/errors.js.map +1 -0
- package/dist/runner/index.d.ts +12 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/index.js +15 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/job-executor.d.ts +98 -0
- package/dist/runner/job-executor.d.ts.map +1 -0
- package/dist/runner/job-executor.js +333 -0
- package/dist/runner/job-executor.js.map +1 -0
- package/dist/runner/message-processor.d.ts +45 -0
- package/dist/runner/message-processor.d.ts.map +1 -0
- package/dist/runner/message-processor.js +294 -0
- package/dist/runner/message-processor.js.map +1 -0
- package/dist/runner/sdk-adapter.d.ts +60 -0
- package/dist/runner/sdk-adapter.d.ts.map +1 -0
- package/dist/runner/sdk-adapter.js +138 -0
- package/dist/runner/sdk-adapter.js.map +1 -0
- package/dist/runner/types.d.ts +135 -0
- package/dist/runner/types.d.ts.map +1 -0
- package/dist/runner/types.js +7 -0
- package/dist/runner/types.js.map +1 -0
- package/dist/scheduler/__tests__/errors.test.d.ts +2 -0
- package/dist/scheduler/__tests__/errors.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/errors.test.js +101 -0
- package/dist/scheduler/__tests__/errors.test.js.map +1 -0
- package/dist/scheduler/__tests__/interval.test.d.ts +2 -0
- package/dist/scheduler/__tests__/interval.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/interval.test.js +419 -0
- package/dist/scheduler/__tests__/interval.test.js.map +1 -0
- package/dist/scheduler/__tests__/schedule-runner.test.d.ts +2 -0
- package/dist/scheduler/__tests__/schedule-runner.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/schedule-runner.test.js +634 -0
- package/dist/scheduler/__tests__/schedule-runner.test.js.map +1 -0
- package/dist/scheduler/__tests__/schedule-state.test.d.ts +2 -0
- package/dist/scheduler/__tests__/schedule-state.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/schedule-state.test.js +572 -0
- package/dist/scheduler/__tests__/schedule-state.test.js.map +1 -0
- package/dist/scheduler/__tests__/scheduler.test.d.ts +2 -0
- package/dist/scheduler/__tests__/scheduler.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/scheduler.test.js +987 -0
- package/dist/scheduler/__tests__/scheduler.test.js.map +1 -0
- package/dist/scheduler/errors.d.ts +61 -0
- package/dist/scheduler/errors.d.ts.map +1 -0
- package/dist/scheduler/errors.js +81 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/index.d.ts +13 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +17 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/interval.d.ts +64 -0
- package/dist/scheduler/interval.d.ts.map +1 -0
- package/dist/scheduler/interval.js +139 -0
- package/dist/scheduler/interval.js.map +1 -0
- package/dist/scheduler/schedule-runner.d.ts +149 -0
- package/dist/scheduler/schedule-runner.d.ts.map +1 -0
- package/dist/scheduler/schedule-runner.js +277 -0
- package/dist/scheduler/schedule-runner.js.map +1 -0
- package/dist/scheduler/schedule-state.d.ts +105 -0
- package/dist/scheduler/schedule-state.d.ts.map +1 -0
- package/dist/scheduler/schedule-state.js +151 -0
- package/dist/scheduler/schedule-state.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +138 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.js +423 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/types.d.ts +160 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/scheduler/types.js +8 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/state/__tests__/directory.test.d.ts +2 -0
- package/dist/state/__tests__/directory.test.d.ts.map +1 -0
- package/dist/state/__tests__/directory.test.js +414 -0
- package/dist/state/__tests__/directory.test.js.map +1 -0
- package/dist/state/__tests__/fleet-state.test.d.ts +2 -0
- package/dist/state/__tests__/fleet-state.test.d.ts.map +1 -0
- package/dist/state/__tests__/fleet-state.test.js +696 -0
- package/dist/state/__tests__/fleet-state.test.js.map +1 -0
- package/dist/state/__tests__/job-metadata-schema.test.d.ts +2 -0
- package/dist/state/__tests__/job-metadata-schema.test.d.ts.map +1 -0
- package/dist/state/__tests__/job-metadata-schema.test.js +329 -0
- package/dist/state/__tests__/job-metadata-schema.test.js.map +1 -0
- package/dist/state/__tests__/job-metadata.test.d.ts +2 -0
- package/dist/state/__tests__/job-metadata.test.d.ts.map +1 -0
- package/dist/state/__tests__/job-metadata.test.js +667 -0
- package/dist/state/__tests__/job-metadata.test.js.map +1 -0
- package/dist/state/__tests__/job-output.test.d.ts +2 -0
- package/dist/state/__tests__/job-output.test.d.ts.map +1 -0
- package/dist/state/__tests__/job-output.test.js +672 -0
- package/dist/state/__tests__/job-output.test.js.map +1 -0
- package/dist/state/__tests__/session-schema.test.d.ts +2 -0
- package/dist/state/__tests__/session-schema.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-schema.test.js +323 -0
- package/dist/state/__tests__/session-schema.test.js.map +1 -0
- package/dist/state/__tests__/session.test.d.ts +2 -0
- package/dist/state/__tests__/session.test.d.ts.map +1 -0
- package/dist/state/__tests__/session.test.js +468 -0
- package/dist/state/__tests__/session.test.js.map +1 -0
- package/dist/state/directory.d.ts +42 -0
- package/dist/state/directory.d.ts.map +1 -0
- package/dist/state/directory.js +170 -0
- package/dist/state/directory.js.map +1 -0
- package/dist/state/errors.d.ts +44 -0
- package/dist/state/errors.d.ts.map +1 -0
- package/dist/state/errors.js +82 -0
- package/dist/state/errors.js.map +1 -0
- package/dist/state/fleet-state.d.ts +126 -0
- package/dist/state/fleet-state.d.ts.map +1 -0
- package/dist/state/fleet-state.js +196 -0
- package/dist/state/fleet-state.js.map +1 -0
- package/dist/state/index.d.ts +21 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +30 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/job-metadata.d.ts +151 -0
- package/dist/state/job-metadata.d.ts.map +1 -0
- package/dist/state/job-metadata.js +287 -0
- package/dist/state/job-metadata.js.map +1 -0
- package/dist/state/job-output.d.ts +116 -0
- package/dist/state/job-output.d.ts.map +1 -0
- package/dist/state/job-output.js +218 -0
- package/dist/state/job-output.js.map +1 -0
- package/dist/state/schemas/__tests__/job-output.test.d.ts +2 -0
- package/dist/state/schemas/__tests__/job-output.test.d.ts.map +1 -0
- package/dist/state/schemas/__tests__/job-output.test.js +279 -0
- package/dist/state/schemas/__tests__/job-output.test.js.map +1 -0
- package/dist/state/schemas/fleet-state.d.ts +249 -0
- package/dist/state/schemas/fleet-state.d.ts.map +1 -0
- package/dist/state/schemas/fleet-state.js +97 -0
- package/dist/state/schemas/fleet-state.js.map +1 -0
- package/dist/state/schemas/index.d.ts +10 -0
- package/dist/state/schemas/index.d.ts.map +1 -0
- package/dist/state/schemas/index.js +10 -0
- package/dist/state/schemas/index.js.map +1 -0
- package/dist/state/schemas/job-metadata.d.ts +118 -0
- package/dist/state/schemas/job-metadata.d.ts.map +1 -0
- package/dist/state/schemas/job-metadata.js +123 -0
- package/dist/state/schemas/job-metadata.js.map +1 -0
- package/dist/state/schemas/job-output.d.ts +291 -0
- package/dist/state/schemas/job-output.d.ts.map +1 -0
- package/dist/state/schemas/job-output.js +132 -0
- package/dist/state/schemas/job-output.js.map +1 -0
- package/dist/state/schemas/session-info.d.ts +65 -0
- package/dist/state/schemas/session-info.d.ts.map +1 -0
- package/dist/state/schemas/session-info.js +58 -0
- package/dist/state/schemas/session-info.js.map +1 -0
- package/dist/state/session.d.ts +92 -0
- package/dist/state/session.d.ts.map +1 -0
- package/dist/state/session.js +173 -0
- package/dist/state/session.js.map +1 -0
- package/dist/state/types.d.ts +54 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +18 -0
- package/dist/state/types.js.map +1 -0
- package/dist/state/utils/__tests__/atomic.test.d.ts +2 -0
- package/dist/state/utils/__tests__/atomic.test.d.ts.map +1 -0
- package/dist/state/utils/__tests__/atomic.test.js +537 -0
- package/dist/state/utils/__tests__/atomic.test.js.map +1 -0
- package/dist/state/utils/__tests__/reads.test.d.ts +2 -0
- package/dist/state/utils/__tests__/reads.test.d.ts.map +1 -0
- package/dist/state/utils/__tests__/reads.test.js +792 -0
- package/dist/state/utils/__tests__/reads.test.js.map +1 -0
- package/dist/state/utils/atomic.d.ts +89 -0
- package/dist/state/utils/atomic.d.ts.map +1 -0
- package/dist/state/utils/atomic.js +157 -0
- package/dist/state/utils/atomic.js.map +1 -0
- package/dist/state/utils/index.d.ts +6 -0
- package/dist/state/utils/index.d.ts.map +1 -0
- package/dist/state/utils/index.js +6 -0
- package/dist/state/utils/index.js.map +1 -0
- package/dist/state/utils/reads.d.ts +196 -0
- package/dist/state/utils/reads.d.ts.map +1 -0
- package/dist/state/utils/reads.js +346 -0
- package/dist/state/utils/reads.js.map +1 -0
- package/dist/work-sources/__tests__/github.test.d.ts +2 -0
- package/dist/work-sources/__tests__/github.test.d.ts.map +1 -0
- package/dist/work-sources/__tests__/github.test.js +1334 -0
- package/dist/work-sources/__tests__/github.test.js.map +1 -0
- package/dist/work-sources/__tests__/manager.test.d.ts +2 -0
- package/dist/work-sources/__tests__/manager.test.d.ts.map +1 -0
- package/dist/work-sources/__tests__/manager.test.js +424 -0
- package/dist/work-sources/__tests__/manager.test.js.map +1 -0
- package/dist/work-sources/__tests__/registry.test.d.ts +2 -0
- package/dist/work-sources/__tests__/registry.test.d.ts.map +1 -0
- package/dist/work-sources/__tests__/registry.test.js +381 -0
- package/dist/work-sources/__tests__/registry.test.js.map +1 -0
- package/dist/work-sources/__tests__/types.test.d.ts +2 -0
- package/dist/work-sources/__tests__/types.test.d.ts.map +1 -0
- package/dist/work-sources/__tests__/types.test.js +406 -0
- package/dist/work-sources/__tests__/types.test.js.map +1 -0
- package/dist/work-sources/adapters/github.d.ts +290 -0
- package/dist/work-sources/adapters/github.d.ts.map +1 -0
- package/dist/work-sources/adapters/github.js +803 -0
- package/dist/work-sources/adapters/github.js.map +1 -0
- package/dist/work-sources/adapters/index.d.ts +10 -0
- package/dist/work-sources/adapters/index.d.ts.map +1 -0
- package/dist/work-sources/adapters/index.js +31 -0
- package/dist/work-sources/adapters/index.js.map +1 -0
- package/dist/work-sources/errors.d.ts +40 -0
- package/dist/work-sources/errors.d.ts.map +1 -0
- package/dist/work-sources/errors.js +54 -0
- package/dist/work-sources/errors.js.map +1 -0
- package/dist/work-sources/index.d.ts +105 -0
- package/dist/work-sources/index.d.ts.map +1 -0
- package/dist/work-sources/index.js +24 -0
- package/dist/work-sources/index.js.map +1 -0
- package/dist/work-sources/manager.d.ts +370 -0
- package/dist/work-sources/manager.d.ts.map +1 -0
- package/dist/work-sources/manager.js +61 -0
- package/dist/work-sources/manager.js.map +1 -0
- package/dist/work-sources/registry.d.ts +128 -0
- package/dist/work-sources/registry.d.ts.map +1 -0
- package/dist/work-sources/registry.js +132 -0
- package/dist/work-sources/registry.js.map +1 -0
- package/dist/work-sources/types.d.ts +127 -0
- package/dist/work-sources/types.d.ts.map +1 -0
- package/dist/work-sources/types.js +8 -0
- package/dist/work-sources/types.js.map +1 -0
- package/package.json +23 -0
- package/src/config/__tests__/agent.test.ts +864 -0
- package/src/config/__tests__/interpolate.test.ts +644 -0
- package/src/config/__tests__/loader.test.ts +784 -0
- package/src/config/__tests__/merge.test.ts +751 -0
- package/src/config/__tests__/parser.test.ts +533 -0
- package/src/config/__tests__/schema.test.ts +873 -0
- package/src/config/index.ts +119 -0
- package/src/config/interpolate.ts +189 -0
- package/src/config/loader.ts +472 -0
- package/src/config/merge.ts +246 -0
- package/src/config/parser.ts +376 -0
- package/src/config/schema.ts +346 -0
- package/src/fleet-manager/__tests__/coverage.test.ts +2869 -0
- package/src/fleet-manager/__tests__/errors.test.ts +660 -0
- package/src/fleet-manager/__tests__/event-helpers.test.ts +448 -0
- package/src/fleet-manager/__tests__/integration.test.ts +1209 -0
- package/src/fleet-manager/__tests__/job-control.test.ts +283 -0
- package/src/fleet-manager/__tests__/job-manager.test.ts +869 -0
- package/src/fleet-manager/__tests__/job-queue.test.ts +401 -0
- package/src/fleet-manager/__tests__/reload.test.ts +751 -0
- package/src/fleet-manager/__tests__/status-queries.test.ts +595 -0
- package/src/fleet-manager/__tests__/trigger.test.ts +601 -0
- package/src/fleet-manager/errors.ts +747 -0
- package/src/fleet-manager/event-types.ts +378 -0
- package/src/fleet-manager/fleet-manager.ts +2315 -0
- package/src/fleet-manager/index.ts +128 -0
- package/src/fleet-manager/job-manager.ts +663 -0
- package/src/fleet-manager/job-queue.ts +798 -0
- package/src/fleet-manager/types.ts +839 -0
- package/src/index.ts +32 -0
- package/src/runner/__tests__/errors.test.ts +382 -0
- package/src/runner/__tests__/job-executor.test.ts +1708 -0
- package/src/runner/__tests__/message-processor.test.ts +960 -0
- package/src/runner/__tests__/sdk-adapter.test.ts +626 -0
- package/src/runner/errors.ts +307 -0
- package/src/runner/index.ts +57 -0
- package/src/runner/job-executor.ts +448 -0
- package/src/runner/message-processor.ts +355 -0
- package/src/runner/sdk-adapter.ts +191 -0
- package/src/runner/types.ts +158 -0
- package/src/scheduler/__tests__/errors.test.ts +159 -0
- package/src/scheduler/__tests__/interval.test.ts +515 -0
- package/src/scheduler/__tests__/schedule-runner.test.ts +798 -0
- package/src/scheduler/__tests__/schedule-state.test.ts +671 -0
- package/src/scheduler/__tests__/scheduler.test.ts +1280 -0
- package/src/scheduler/errors.ts +101 -0
- package/src/scheduler/index.ts +53 -0
- package/src/scheduler/interval.ts +189 -0
- package/src/scheduler/schedule-runner.ts +442 -0
- package/src/scheduler/schedule-state.ts +211 -0
- package/src/scheduler/scheduler.ts +570 -0
- package/src/scheduler/types.ts +216 -0
- package/src/state/__tests__/directory.test.ts +595 -0
- package/src/state/__tests__/fleet-state.test.ts +868 -0
- package/src/state/__tests__/job-metadata-schema.test.ts +414 -0
- package/src/state/__tests__/job-metadata.test.ts +831 -0
- package/src/state/__tests__/job-output.test.ts +856 -0
- package/src/state/__tests__/session-schema.test.ts +378 -0
- package/src/state/__tests__/session.test.ts +604 -0
- package/src/state/directory.ts +217 -0
- package/src/state/errors.ts +97 -0
- package/src/state/fleet-state.ts +284 -0
- package/src/state/index.ts +79 -0
- package/src/state/job-metadata.ts +445 -0
- package/src/state/job-output.ts +316 -0
- package/src/state/schemas/__tests__/job-output.test.ts +338 -0
- package/src/state/schemas/fleet-state.ts +120 -0
- package/src/state/schemas/index.ts +67 -0
- package/src/state/schemas/job-metadata.ts +181 -0
- package/src/state/schemas/job-output.ts +177 -0
- package/src/state/schemas/session-info.ts +92 -0
- package/src/state/session.ts +253 -0
- package/src/state/types.ts +59 -0
- package/src/state/utils/__tests__/atomic.test.ts +723 -0
- package/src/state/utils/__tests__/reads.test.ts +1071 -0
- package/src/state/utils/atomic.ts +221 -0
- package/src/state/utils/index.ts +6 -0
- package/src/state/utils/reads.ts +512 -0
- package/src/work-sources/__tests__/github.test.ts +1800 -0
- package/src/work-sources/__tests__/manager.test.ts +529 -0
- package/src/work-sources/__tests__/registry.test.ts +477 -0
- package/src/work-sources/__tests__/types.test.ts +479 -0
- package/src/work-sources/adapters/github.ts +1166 -0
- package/src/work-sources/adapters/index.ts +64 -0
- package/src/work-sources/errors.ts +71 -0
- package/src/work-sources/index.ts +148 -0
- package/src/work-sources/manager.ts +413 -0
- package/src/work-sources/registry.ts +178 -0
- package/src/work-sources/types.ts +161 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,2869 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additional tests for FleetManager to improve code coverage
|
|
3
|
+
*
|
|
4
|
+
* Targets specific uncovered code paths:
|
|
5
|
+
* - Error handling in startSchedulerAsync
|
|
6
|
+
* - Error handling in handleScheduleTrigger
|
|
7
|
+
* - Default logger usage
|
|
8
|
+
* - Log streaming methods edge cases
|
|
9
|
+
* - ConcurrencyLimitError paths
|
|
10
|
+
* - Configuration error paths
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
14
|
+
import { mkdtemp, rm, mkdir, writeFile } from "fs/promises";
|
|
15
|
+
import { tmpdir } from "os";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import { FleetManager } from "../fleet-manager.js";
|
|
18
|
+
import {
|
|
19
|
+
ConcurrencyLimitError,
|
|
20
|
+
JobCancelError,
|
|
21
|
+
FleetManagerConfigError,
|
|
22
|
+
FleetManagerStateDirError,
|
|
23
|
+
AgentNotFoundError,
|
|
24
|
+
ScheduleNotFoundError,
|
|
25
|
+
} from "../errors.js";
|
|
26
|
+
import type { FleetManagerLogger } from "../types.js";
|
|
27
|
+
|
|
28
|
+
describe("FleetManager Coverage Tests", () => {
|
|
29
|
+
let tempDir: string;
|
|
30
|
+
let configDir: string;
|
|
31
|
+
let stateDir: string;
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
tempDir = await mkdtemp(join(tmpdir(), "fleet-coverage-test-"));
|
|
35
|
+
configDir = join(tempDir, "config");
|
|
36
|
+
stateDir = join(tempDir, ".herdctl");
|
|
37
|
+
await mkdir(configDir, { recursive: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
async function createConfig(config: object) {
|
|
45
|
+
const configPath = join(configDir, "herdctl.yaml");
|
|
46
|
+
const yaml = await import("yaml");
|
|
47
|
+
await writeFile(configPath, yaml.stringify(config));
|
|
48
|
+
return configPath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function createAgentConfig(name: string, config: object) {
|
|
52
|
+
const agentDir = join(configDir, "agents");
|
|
53
|
+
await mkdir(agentDir, { recursive: true });
|
|
54
|
+
const agentPath = join(agentDir, `${name}.yaml`);
|
|
55
|
+
const yaml = await import("yaml");
|
|
56
|
+
await writeFile(agentPath, yaml.stringify(config));
|
|
57
|
+
return agentPath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createSilentLogger(): FleetManagerLogger {
|
|
61
|
+
return {
|
|
62
|
+
debug: vi.fn(),
|
|
63
|
+
info: vi.fn(),
|
|
64
|
+
warn: vi.fn(),
|
|
65
|
+
error: vi.fn(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ===========================================================================
|
|
70
|
+
// Default Logger Tests
|
|
71
|
+
// ===========================================================================
|
|
72
|
+
describe("Default logger", () => {
|
|
73
|
+
it("uses default console logger when none provided", async () => {
|
|
74
|
+
// Mock console methods
|
|
75
|
+
const originalDebug = console.debug;
|
|
76
|
+
const originalInfo = console.info;
|
|
77
|
+
const originalWarn = console.warn;
|
|
78
|
+
const originalError = console.error;
|
|
79
|
+
|
|
80
|
+
const debugSpy = vi.fn();
|
|
81
|
+
const infoSpy = vi.fn();
|
|
82
|
+
const warnSpy = vi.fn();
|
|
83
|
+
const errorSpy = vi.fn();
|
|
84
|
+
|
|
85
|
+
console.debug = debugSpy;
|
|
86
|
+
console.info = infoSpy;
|
|
87
|
+
console.warn = warnSpy;
|
|
88
|
+
console.error = errorSpy;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await createAgentConfig("test-agent", {
|
|
92
|
+
name: "test-agent",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const configPath = await createConfig({
|
|
96
|
+
version: 1,
|
|
97
|
+
agents: [{ path: "./agents/test-agent.yaml" }],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Create manager without logger - uses default
|
|
101
|
+
const manager = new FleetManager({
|
|
102
|
+
configPath,
|
|
103
|
+
stateDir,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await manager.initialize();
|
|
107
|
+
|
|
108
|
+
// Default logger should have logged to console.info
|
|
109
|
+
expect(infoSpy).toHaveBeenCalled();
|
|
110
|
+
expect(infoSpy.mock.calls.some((call) =>
|
|
111
|
+
call[0].includes("[fleet-manager]")
|
|
112
|
+
)).toBe(true);
|
|
113
|
+
} finally {
|
|
114
|
+
// Restore console methods
|
|
115
|
+
console.debug = originalDebug;
|
|
116
|
+
console.info = originalInfo;
|
|
117
|
+
console.warn = originalWarn;
|
|
118
|
+
console.error = originalError;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("default logger debug method works", async () => {
|
|
123
|
+
const originalDebug = console.debug;
|
|
124
|
+
const debugSpy = vi.fn();
|
|
125
|
+
console.debug = debugSpy;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await createAgentConfig("debug-agent", {
|
|
129
|
+
name: "debug-agent",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const configPath = await createConfig({
|
|
133
|
+
version: 1,
|
|
134
|
+
agents: [{ path: "./agents/debug-agent.yaml" }],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const manager = new FleetManager({
|
|
138
|
+
configPath,
|
|
139
|
+
stateDir,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await manager.initialize();
|
|
143
|
+
|
|
144
|
+
// Debug should have been called with loading config message
|
|
145
|
+
expect(debugSpy).toHaveBeenCalled();
|
|
146
|
+
} finally {
|
|
147
|
+
console.debug = originalDebug;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ===========================================================================
|
|
153
|
+
// ConcurrencyLimitError Tests
|
|
154
|
+
// ===========================================================================
|
|
155
|
+
describe("ConcurrencyLimitError in trigger", () => {
|
|
156
|
+
it("ConcurrencyLimitError has correct properties", async () => {
|
|
157
|
+
// Test the error class directly
|
|
158
|
+
const error = new ConcurrencyLimitError("limited-agent", 1, 1);
|
|
159
|
+
expect(error.name).toBe("ConcurrencyLimitError");
|
|
160
|
+
expect(error.agentName).toBe("limited-agent");
|
|
161
|
+
expect(error.currentJobs).toBe(1);
|
|
162
|
+
expect(error.limit).toBe(1);
|
|
163
|
+
expect(error.isAtLimit()).toBe(true);
|
|
164
|
+
expect(error.message).toContain("limited-agent");
|
|
165
|
+
expect(error.message).toContain("concurrency limit");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ===========================================================================
|
|
170
|
+
// Configuration Error Handling
|
|
171
|
+
// ===========================================================================
|
|
172
|
+
describe("Configuration error handling", () => {
|
|
173
|
+
it("wraps ConfigNotFoundError in FleetManagerConfigError", async () => {
|
|
174
|
+
const manager = new FleetManager({
|
|
175
|
+
configPath: "/nonexistent/path/config.yaml",
|
|
176
|
+
stateDir,
|
|
177
|
+
logger: createSilentLogger(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await expect(manager.initialize()).rejects.toThrow(FleetManagerConfigError);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("wraps ConfigError in FleetManagerConfigError", async () => {
|
|
184
|
+
const configPath = join(configDir, "herdctl.yaml");
|
|
185
|
+
// Invalid YAML
|
|
186
|
+
await writeFile(configPath, "invalid: yaml: content: [:");
|
|
187
|
+
|
|
188
|
+
const manager = new FleetManager({
|
|
189
|
+
configPath,
|
|
190
|
+
stateDir,
|
|
191
|
+
logger: createSilentLogger(),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await expect(manager.initialize()).rejects.toThrow(FleetManagerConfigError);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("wraps unknown errors in FleetManagerConfigError", async () => {
|
|
198
|
+
// Create a config that will cause an unexpected error
|
|
199
|
+
const configPath = join(configDir, "herdctl.yaml");
|
|
200
|
+
await writeFile(configPath, "version: 1\nagents: 'not-an-array'");
|
|
201
|
+
|
|
202
|
+
const manager = new FleetManager({
|
|
203
|
+
configPath,
|
|
204
|
+
stateDir,
|
|
205
|
+
logger: createSilentLogger(),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await expect(manager.initialize()).rejects.toThrow(FleetManagerConfigError);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// ===========================================================================
|
|
213
|
+
// Log Streaming Tests
|
|
214
|
+
// ===========================================================================
|
|
215
|
+
describe("Log streaming edge cases", () => {
|
|
216
|
+
it("streamLogs returns async iterable", async () => {
|
|
217
|
+
await createAgentConfig("stream-agent", {
|
|
218
|
+
name: "stream-agent",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const configPath = await createConfig({
|
|
222
|
+
version: 1,
|
|
223
|
+
agents: [{ path: "./agents/stream-agent.yaml" }],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const manager = new FleetManager({
|
|
227
|
+
configPath,
|
|
228
|
+
stateDir,
|
|
229
|
+
logger: createSilentLogger(),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await manager.initialize();
|
|
233
|
+
|
|
234
|
+
const stream = manager.streamLogs({ includeHistory: false });
|
|
235
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("streamLogs with level filter", async () => {
|
|
239
|
+
await createAgentConfig("level-filter-agent", {
|
|
240
|
+
name: "level-filter-agent",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const configPath = await createConfig({
|
|
244
|
+
version: 1,
|
|
245
|
+
agents: [{ path: "./agents/level-filter-agent.yaml" }],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const manager = new FleetManager({
|
|
249
|
+
configPath,
|
|
250
|
+
stateDir,
|
|
251
|
+
logger: createSilentLogger(),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await manager.initialize();
|
|
255
|
+
|
|
256
|
+
// Test different log levels
|
|
257
|
+
const errorStream = manager.streamLogs({ level: "error", includeHistory: false });
|
|
258
|
+
expect(errorStream[Symbol.asyncIterator]).toBeDefined();
|
|
259
|
+
|
|
260
|
+
const warnStream = manager.streamLogs({ level: "warn", includeHistory: false });
|
|
261
|
+
expect(warnStream[Symbol.asyncIterator]).toBeDefined();
|
|
262
|
+
|
|
263
|
+
const debugStream = manager.streamLogs({ level: "debug", includeHistory: false });
|
|
264
|
+
expect(debugStream[Symbol.asyncIterator]).toBeDefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("streamLogs with agent filter", async () => {
|
|
268
|
+
await createAgentConfig("filter-agent", {
|
|
269
|
+
name: "filter-agent",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const configPath = await createConfig({
|
|
273
|
+
version: 1,
|
|
274
|
+
agents: [{ path: "./agents/filter-agent.yaml" }],
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const manager = new FleetManager({
|
|
278
|
+
configPath,
|
|
279
|
+
stateDir,
|
|
280
|
+
logger: createSilentLogger(),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await manager.initialize();
|
|
284
|
+
|
|
285
|
+
const stream = manager.streamLogs({
|
|
286
|
+
agentName: "filter-agent",
|
|
287
|
+
includeHistory: false,
|
|
288
|
+
});
|
|
289
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("streamLogs with job filter", async () => {
|
|
293
|
+
await createAgentConfig("job-filter-agent", {
|
|
294
|
+
name: "job-filter-agent",
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const configPath = await createConfig({
|
|
298
|
+
version: 1,
|
|
299
|
+
agents: [{ path: "./agents/job-filter-agent.yaml" }],
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const manager = new FleetManager({
|
|
303
|
+
configPath,
|
|
304
|
+
stateDir,
|
|
305
|
+
logger: createSilentLogger(),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
await manager.initialize();
|
|
309
|
+
|
|
310
|
+
const stream = manager.streamLogs({
|
|
311
|
+
jobId: "job-2024-01-15-abc123",
|
|
312
|
+
includeHistory: false,
|
|
313
|
+
});
|
|
314
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("streamLogs with history limit", async () => {
|
|
318
|
+
await createAgentConfig("history-limit-agent", {
|
|
319
|
+
name: "history-limit-agent",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const configPath = await createConfig({
|
|
323
|
+
version: 1,
|
|
324
|
+
agents: [{ path: "./agents/history-limit-agent.yaml" }],
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const manager = new FleetManager({
|
|
328
|
+
configPath,
|
|
329
|
+
stateDir,
|
|
330
|
+
logger: createSilentLogger(),
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await manager.initialize();
|
|
334
|
+
|
|
335
|
+
// Create some jobs first
|
|
336
|
+
await manager.trigger("history-limit-agent");
|
|
337
|
+
await manager.trigger("history-limit-agent");
|
|
338
|
+
|
|
339
|
+
const stream = manager.streamLogs({
|
|
340
|
+
includeHistory: true,
|
|
341
|
+
historyLimit: 5,
|
|
342
|
+
});
|
|
343
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("streamJobOutput returns async iterable", async () => {
|
|
347
|
+
await createAgentConfig("job-output-agent", {
|
|
348
|
+
name: "job-output-agent",
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const configPath = await createConfig({
|
|
352
|
+
version: 1,
|
|
353
|
+
agents: [{ path: "./agents/job-output-agent.yaml" }],
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const manager = new FleetManager({
|
|
357
|
+
configPath,
|
|
358
|
+
stateDir,
|
|
359
|
+
logger: createSilentLogger(),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await manager.initialize();
|
|
363
|
+
|
|
364
|
+
// Trigger a job
|
|
365
|
+
const result = await manager.trigger("job-output-agent");
|
|
366
|
+
|
|
367
|
+
const stream = manager.streamJobOutput(result.jobId);
|
|
368
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("streamAgentLogs returns async iterable", async () => {
|
|
372
|
+
await createAgentConfig("agent-logs-agent", {
|
|
373
|
+
name: "agent-logs-agent",
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const configPath = await createConfig({
|
|
377
|
+
version: 1,
|
|
378
|
+
agents: [{ path: "./agents/agent-logs-agent.yaml" }],
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const manager = new FleetManager({
|
|
382
|
+
configPath,
|
|
383
|
+
stateDir,
|
|
384
|
+
logger: createSilentLogger(),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
await manager.initialize();
|
|
388
|
+
|
|
389
|
+
const stream = manager.streamAgentLogs("agent-logs-agent");
|
|
390
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// ===========================================================================
|
|
395
|
+
// handleScheduleTrigger Error Handling
|
|
396
|
+
// ===========================================================================
|
|
397
|
+
describe("Schedule trigger error handling", () => {
|
|
398
|
+
it("emits schedule:trigger and schedule:complete events", async () => {
|
|
399
|
+
await createAgentConfig("trigger-event-agent", {
|
|
400
|
+
name: "trigger-event-agent",
|
|
401
|
+
schedules: {
|
|
402
|
+
test: {
|
|
403
|
+
type: "interval",
|
|
404
|
+
interval: "100ms",
|
|
405
|
+
prompt: "Test prompt",
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const configPath = await createConfig({
|
|
411
|
+
version: 1,
|
|
412
|
+
agents: [{ path: "./agents/trigger-event-agent.yaml" }],
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const manager = new FleetManager({
|
|
416
|
+
configPath,
|
|
417
|
+
stateDir,
|
|
418
|
+
checkInterval: 50,
|
|
419
|
+
logger: createSilentLogger(),
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const triggerHandler = vi.fn();
|
|
423
|
+
const triggeredHandler = vi.fn();
|
|
424
|
+
const completeHandler = vi.fn();
|
|
425
|
+
|
|
426
|
+
manager.on("schedule:trigger", triggerHandler);
|
|
427
|
+
manager.on("schedule:triggered", triggeredHandler);
|
|
428
|
+
manager.on("schedule:complete", completeHandler);
|
|
429
|
+
|
|
430
|
+
await manager.initialize();
|
|
431
|
+
await manager.start();
|
|
432
|
+
|
|
433
|
+
// Wait for schedule to trigger
|
|
434
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
435
|
+
|
|
436
|
+
await manager.stop();
|
|
437
|
+
|
|
438
|
+
// Both legacy and new events should be emitted
|
|
439
|
+
expect(triggerHandler).toHaveBeenCalled();
|
|
440
|
+
expect(triggeredHandler).toHaveBeenCalled();
|
|
441
|
+
expect(completeHandler).toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// ===========================================================================
|
|
446
|
+
// Scheduler Error Handling (startSchedulerAsync)
|
|
447
|
+
// ===========================================================================
|
|
448
|
+
describe("startSchedulerAsync error handling", () => {
|
|
449
|
+
it("handles scheduler errors and sets error state", async () => {
|
|
450
|
+
await createAgentConfig("error-agent", {
|
|
451
|
+
name: "error-agent",
|
|
452
|
+
schedules: {
|
|
453
|
+
test: {
|
|
454
|
+
type: "interval",
|
|
455
|
+
interval: "100ms",
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const configPath = await createConfig({
|
|
461
|
+
version: 1,
|
|
462
|
+
agents: [{ path: "./agents/error-agent.yaml" }],
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const logger = createSilentLogger();
|
|
466
|
+
const manager = new FleetManager({
|
|
467
|
+
configPath,
|
|
468
|
+
stateDir,
|
|
469
|
+
checkInterval: 50,
|
|
470
|
+
logger,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const errorHandler = vi.fn();
|
|
474
|
+
manager.on("error", errorHandler);
|
|
475
|
+
|
|
476
|
+
await manager.initialize();
|
|
477
|
+
await manager.start();
|
|
478
|
+
|
|
479
|
+
// Let it run briefly
|
|
480
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
481
|
+
|
|
482
|
+
await manager.stop();
|
|
483
|
+
|
|
484
|
+
// The manager should still be in stopped state
|
|
485
|
+
expect(manager.state.status).toBe("stopped");
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// ===========================================================================
|
|
490
|
+
// Config Change Detection Edge Cases
|
|
491
|
+
// ===========================================================================
|
|
492
|
+
describe("Config change detection edge cases", () => {
|
|
493
|
+
it("detects workspace changes between string and object forms", async () => {
|
|
494
|
+
await createAgentConfig("workspace-agent", {
|
|
495
|
+
name: "workspace-agent",
|
|
496
|
+
workspace: "/simple/path",
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const configPath = await createConfig({
|
|
500
|
+
version: 1,
|
|
501
|
+
agents: [{ path: "./agents/workspace-agent.yaml" }],
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const manager = new FleetManager({
|
|
505
|
+
configPath,
|
|
506
|
+
stateDir,
|
|
507
|
+
logger: createSilentLogger(),
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
await manager.initialize();
|
|
511
|
+
|
|
512
|
+
// Modify to object workspace
|
|
513
|
+
await createAgentConfig("workspace-agent", {
|
|
514
|
+
name: "workspace-agent",
|
|
515
|
+
workspace: {
|
|
516
|
+
root: "/object/path",
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const result = await manager.reload();
|
|
521
|
+
|
|
522
|
+
expect(result.changes).toContainEqual(
|
|
523
|
+
expect.objectContaining({
|
|
524
|
+
type: "modified",
|
|
525
|
+
category: "agent",
|
|
526
|
+
name: "workspace-agent",
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("detects max_turns changes", async () => {
|
|
532
|
+
await createAgentConfig("turns-agent", {
|
|
533
|
+
name: "turns-agent",
|
|
534
|
+
max_turns: 10,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const configPath = await createConfig({
|
|
538
|
+
version: 1,
|
|
539
|
+
agents: [{ path: "./agents/turns-agent.yaml" }],
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const manager = new FleetManager({
|
|
543
|
+
configPath,
|
|
544
|
+
stateDir,
|
|
545
|
+
logger: createSilentLogger(),
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
await manager.initialize();
|
|
549
|
+
|
|
550
|
+
// Modify max_turns
|
|
551
|
+
await createAgentConfig("turns-agent", {
|
|
552
|
+
name: "turns-agent",
|
|
553
|
+
max_turns: 20,
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const result = await manager.reload();
|
|
557
|
+
|
|
558
|
+
expect(result.changes).toContainEqual(
|
|
559
|
+
expect.objectContaining({
|
|
560
|
+
type: "modified",
|
|
561
|
+
category: "agent",
|
|
562
|
+
name: "turns-agent",
|
|
563
|
+
details: expect.stringContaining("max_turns"),
|
|
564
|
+
})
|
|
565
|
+
);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it("detects system_prompt changes", async () => {
|
|
569
|
+
await createAgentConfig("prompt-agent", {
|
|
570
|
+
name: "prompt-agent",
|
|
571
|
+
system_prompt: "Original system prompt",
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const configPath = await createConfig({
|
|
575
|
+
version: 1,
|
|
576
|
+
agents: [{ path: "./agents/prompt-agent.yaml" }],
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const manager = new FleetManager({
|
|
580
|
+
configPath,
|
|
581
|
+
stateDir,
|
|
582
|
+
logger: createSilentLogger(),
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
await manager.initialize();
|
|
586
|
+
|
|
587
|
+
// Modify system_prompt
|
|
588
|
+
await createAgentConfig("prompt-agent", {
|
|
589
|
+
name: "prompt-agent",
|
|
590
|
+
system_prompt: "Updated system prompt",
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const result = await manager.reload();
|
|
594
|
+
|
|
595
|
+
expect(result.changes).toContainEqual(
|
|
596
|
+
expect.objectContaining({
|
|
597
|
+
type: "modified",
|
|
598
|
+
category: "agent",
|
|
599
|
+
name: "prompt-agent",
|
|
600
|
+
details: expect.stringContaining("system_prompt"),
|
|
601
|
+
})
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it("detects max_concurrent changes", async () => {
|
|
606
|
+
await createAgentConfig("concurrent-agent", {
|
|
607
|
+
name: "concurrent-agent",
|
|
608
|
+
instances: { max_concurrent: 2 },
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const configPath = await createConfig({
|
|
612
|
+
version: 1,
|
|
613
|
+
agents: [{ path: "./agents/concurrent-agent.yaml" }],
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const manager = new FleetManager({
|
|
617
|
+
configPath,
|
|
618
|
+
stateDir,
|
|
619
|
+
logger: createSilentLogger(),
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
await manager.initialize();
|
|
623
|
+
|
|
624
|
+
// Modify max_concurrent
|
|
625
|
+
await createAgentConfig("concurrent-agent", {
|
|
626
|
+
name: "concurrent-agent",
|
|
627
|
+
instances: { max_concurrent: 5 },
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const result = await manager.reload();
|
|
631
|
+
|
|
632
|
+
expect(result.changes).toContainEqual(
|
|
633
|
+
expect.objectContaining({
|
|
634
|
+
type: "modified",
|
|
635
|
+
category: "agent",
|
|
636
|
+
name: "concurrent-agent",
|
|
637
|
+
details: expect.stringContaining("max_concurrent"),
|
|
638
|
+
})
|
|
639
|
+
);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it("detects schedule type changes", async () => {
|
|
643
|
+
await createAgentConfig("type-change-agent", {
|
|
644
|
+
name: "type-change-agent",
|
|
645
|
+
schedules: {
|
|
646
|
+
check: {
|
|
647
|
+
type: "interval",
|
|
648
|
+
interval: "1h",
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const configPath = await createConfig({
|
|
654
|
+
version: 1,
|
|
655
|
+
agents: [{ path: "./agents/type-change-agent.yaml" }],
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
const manager = new FleetManager({
|
|
659
|
+
configPath,
|
|
660
|
+
stateDir,
|
|
661
|
+
logger: createSilentLogger(),
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
await manager.initialize();
|
|
665
|
+
|
|
666
|
+
// Change to cron type
|
|
667
|
+
await createAgentConfig("type-change-agent", {
|
|
668
|
+
name: "type-change-agent",
|
|
669
|
+
schedules: {
|
|
670
|
+
check: {
|
|
671
|
+
type: "cron",
|
|
672
|
+
expression: "0 * * * *",
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const result = await manager.reload();
|
|
678
|
+
|
|
679
|
+
expect(result.changes).toContainEqual(
|
|
680
|
+
expect.objectContaining({
|
|
681
|
+
type: "modified",
|
|
682
|
+
category: "schedule",
|
|
683
|
+
name: "type-change-agent/check",
|
|
684
|
+
})
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("detects schedule expression changes", async () => {
|
|
689
|
+
await createAgentConfig("expr-agent", {
|
|
690
|
+
name: "expr-agent",
|
|
691
|
+
schedules: {
|
|
692
|
+
check: {
|
|
693
|
+
type: "cron",
|
|
694
|
+
expression: "0 * * * *",
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const configPath = await createConfig({
|
|
700
|
+
version: 1,
|
|
701
|
+
agents: [{ path: "./agents/expr-agent.yaml" }],
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const manager = new FleetManager({
|
|
705
|
+
configPath,
|
|
706
|
+
stateDir,
|
|
707
|
+
logger: createSilentLogger(),
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
await manager.initialize();
|
|
711
|
+
|
|
712
|
+
// Change expression
|
|
713
|
+
await createAgentConfig("expr-agent", {
|
|
714
|
+
name: "expr-agent",
|
|
715
|
+
schedules: {
|
|
716
|
+
check: {
|
|
717
|
+
type: "cron",
|
|
718
|
+
expression: "30 * * * *",
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const result = await manager.reload();
|
|
724
|
+
|
|
725
|
+
expect(result.changes).toContainEqual(
|
|
726
|
+
expect.objectContaining({
|
|
727
|
+
type: "modified",
|
|
728
|
+
category: "schedule",
|
|
729
|
+
name: "expr-agent/check",
|
|
730
|
+
details: expect.stringContaining("expression"),
|
|
731
|
+
})
|
|
732
|
+
);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it("handles added agent with schedules", async () => {
|
|
736
|
+
await createAgentConfig("original-agent", {
|
|
737
|
+
name: "original-agent",
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const configPath = await createConfig({
|
|
741
|
+
version: 1,
|
|
742
|
+
agents: [{ path: "./agents/original-agent.yaml" }],
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
const manager = new FleetManager({
|
|
746
|
+
configPath,
|
|
747
|
+
stateDir,
|
|
748
|
+
logger: createSilentLogger(),
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
await manager.initialize();
|
|
752
|
+
|
|
753
|
+
// Add new agent with schedules
|
|
754
|
+
await createAgentConfig("new-agent-with-schedules", {
|
|
755
|
+
name: "new-agent-with-schedules",
|
|
756
|
+
schedules: {
|
|
757
|
+
hourly: {
|
|
758
|
+
type: "interval",
|
|
759
|
+
interval: "1h",
|
|
760
|
+
},
|
|
761
|
+
daily: {
|
|
762
|
+
type: "interval",
|
|
763
|
+
interval: "24h",
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
await createConfig({
|
|
769
|
+
version: 1,
|
|
770
|
+
agents: [
|
|
771
|
+
{ path: "./agents/original-agent.yaml" },
|
|
772
|
+
{ path: "./agents/new-agent-with-schedules.yaml" },
|
|
773
|
+
],
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
const result = await manager.reload();
|
|
777
|
+
|
|
778
|
+
// Should have agent added
|
|
779
|
+
expect(result.changes).toContainEqual(
|
|
780
|
+
expect.objectContaining({
|
|
781
|
+
type: "added",
|
|
782
|
+
category: "agent",
|
|
783
|
+
name: "new-agent-with-schedules",
|
|
784
|
+
})
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
// Should have both schedules added
|
|
788
|
+
expect(result.changes).toContainEqual(
|
|
789
|
+
expect.objectContaining({
|
|
790
|
+
type: "added",
|
|
791
|
+
category: "schedule",
|
|
792
|
+
name: "new-agent-with-schedules/hourly",
|
|
793
|
+
})
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
expect(result.changes).toContainEqual(
|
|
797
|
+
expect.objectContaining({
|
|
798
|
+
type: "added",
|
|
799
|
+
category: "schedule",
|
|
800
|
+
name: "new-agent-with-schedules/daily",
|
|
801
|
+
})
|
|
802
|
+
);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it("handles removed agent with schedules", async () => {
|
|
806
|
+
await createAgentConfig("keep-agent", {
|
|
807
|
+
name: "keep-agent",
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
await createAgentConfig("remove-agent", {
|
|
811
|
+
name: "remove-agent",
|
|
812
|
+
schedules: {
|
|
813
|
+
hourly: {
|
|
814
|
+
type: "interval",
|
|
815
|
+
interval: "1h",
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
const configPath = await createConfig({
|
|
821
|
+
version: 1,
|
|
822
|
+
agents: [
|
|
823
|
+
{ path: "./agents/keep-agent.yaml" },
|
|
824
|
+
{ path: "./agents/remove-agent.yaml" },
|
|
825
|
+
],
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const manager = new FleetManager({
|
|
829
|
+
configPath,
|
|
830
|
+
stateDir,
|
|
831
|
+
logger: createSilentLogger(),
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
await manager.initialize();
|
|
835
|
+
|
|
836
|
+
// Remove the agent
|
|
837
|
+
await createConfig({
|
|
838
|
+
version: 1,
|
|
839
|
+
agents: [{ path: "./agents/keep-agent.yaml" }],
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
const result = await manager.reload();
|
|
843
|
+
|
|
844
|
+
// Should have agent removed
|
|
845
|
+
expect(result.changes).toContainEqual(
|
|
846
|
+
expect.objectContaining({
|
|
847
|
+
type: "removed",
|
|
848
|
+
category: "agent",
|
|
849
|
+
name: "remove-agent",
|
|
850
|
+
})
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
// Should have schedule removed
|
|
854
|
+
expect(result.changes).toContainEqual(
|
|
855
|
+
expect.objectContaining({
|
|
856
|
+
type: "removed",
|
|
857
|
+
category: "schedule",
|
|
858
|
+
name: "remove-agent/hourly",
|
|
859
|
+
})
|
|
860
|
+
);
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
// ===========================================================================
|
|
865
|
+
// Stop Options Tests
|
|
866
|
+
// ===========================================================================
|
|
867
|
+
describe("Stop options", () => {
|
|
868
|
+
it("stop with waitForJobs=false", async () => {
|
|
869
|
+
await createAgentConfig("no-wait-agent", {
|
|
870
|
+
name: "no-wait-agent",
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
const configPath = await createConfig({
|
|
874
|
+
version: 1,
|
|
875
|
+
agents: [{ path: "./agents/no-wait-agent.yaml" }],
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
const manager = new FleetManager({
|
|
879
|
+
configPath,
|
|
880
|
+
stateDir,
|
|
881
|
+
checkInterval: 10000,
|
|
882
|
+
logger: createSilentLogger(),
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
await manager.initialize();
|
|
886
|
+
await manager.start();
|
|
887
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
888
|
+
|
|
889
|
+
await manager.stop({ waitForJobs: false });
|
|
890
|
+
|
|
891
|
+
expect(manager.state.status).toBe("stopped");
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it("stop with cancelOnTimeout cancels jobs on timeout", async () => {
|
|
895
|
+
await createAgentConfig("cancel-timeout-agent", {
|
|
896
|
+
name: "cancel-timeout-agent",
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const configPath = await createConfig({
|
|
900
|
+
version: 1,
|
|
901
|
+
agents: [{ path: "./agents/cancel-timeout-agent.yaml" }],
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
const logger = createSilentLogger();
|
|
905
|
+
const manager = new FleetManager({
|
|
906
|
+
configPath,
|
|
907
|
+
stateDir,
|
|
908
|
+
checkInterval: 10000,
|
|
909
|
+
logger,
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
await manager.initialize();
|
|
913
|
+
await manager.start();
|
|
914
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
915
|
+
|
|
916
|
+
// Create a job
|
|
917
|
+
await manager.trigger("cancel-timeout-agent");
|
|
918
|
+
|
|
919
|
+
await manager.stop({
|
|
920
|
+
timeout: 100,
|
|
921
|
+
cancelOnTimeout: true,
|
|
922
|
+
cancelTimeout: 50,
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
expect(manager.state.status).toBe("stopped");
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// ===========================================================================
|
|
930
|
+
// Fleet Status Edge Cases
|
|
931
|
+
// ===========================================================================
|
|
932
|
+
describe("Fleet status edge cases", () => {
|
|
933
|
+
it("computeFleetCounts handles different agent states", async () => {
|
|
934
|
+
await createAgentConfig("count-agent", {
|
|
935
|
+
name: "count-agent",
|
|
936
|
+
schedules: {
|
|
937
|
+
test: {
|
|
938
|
+
type: "interval",
|
|
939
|
+
interval: "1h",
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
const configPath = await createConfig({
|
|
945
|
+
version: 1,
|
|
946
|
+
agents: [{ path: "./agents/count-agent.yaml" }],
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
const manager = new FleetManager({
|
|
950
|
+
configPath,
|
|
951
|
+
stateDir,
|
|
952
|
+
logger: createSilentLogger(),
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
await manager.initialize();
|
|
956
|
+
|
|
957
|
+
const status = await manager.getFleetStatus();
|
|
958
|
+
|
|
959
|
+
expect(status.counts.totalAgents).toBe(1);
|
|
960
|
+
expect(status.counts.idleAgents).toBe(1);
|
|
961
|
+
expect(status.counts.runningAgents).toBe(0);
|
|
962
|
+
expect(status.counts.errorAgents).toBe(0);
|
|
963
|
+
expect(status.counts.totalSchedules).toBe(1);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it("getFleetStatus computes uptime correctly when stopped", async () => {
|
|
967
|
+
await createAgentConfig("uptime-agent", {
|
|
968
|
+
name: "uptime-agent",
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
const configPath = await createConfig({
|
|
972
|
+
version: 1,
|
|
973
|
+
agents: [{ path: "./agents/uptime-agent.yaml" }],
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const manager = new FleetManager({
|
|
977
|
+
configPath,
|
|
978
|
+
stateDir,
|
|
979
|
+
checkInterval: 10000,
|
|
980
|
+
logger: createSilentLogger(),
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
await manager.initialize();
|
|
984
|
+
await manager.start();
|
|
985
|
+
|
|
986
|
+
// Wait a bit to accumulate uptime
|
|
987
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
988
|
+
|
|
989
|
+
const runningStatus = await manager.getFleetStatus();
|
|
990
|
+
expect(runningStatus.uptimeSeconds).toBeGreaterThanOrEqual(0);
|
|
991
|
+
|
|
992
|
+
await manager.stop();
|
|
993
|
+
|
|
994
|
+
// Uptime should still be calculated after stop
|
|
995
|
+
const stoppedStatus = await manager.getFleetStatus();
|
|
996
|
+
expect(stoppedStatus.uptimeSeconds).toBeGreaterThanOrEqual(0);
|
|
997
|
+
});
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// ===========================================================================
|
|
1001
|
+
// cancelJob Additional Tests
|
|
1002
|
+
// ===========================================================================
|
|
1003
|
+
describe("cancelJob additional tests", () => {
|
|
1004
|
+
it("calculates duration correctly for already stopped jobs", async () => {
|
|
1005
|
+
await createAgentConfig("duration-agent", {
|
|
1006
|
+
name: "duration-agent",
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
const configPath = await createConfig({
|
|
1010
|
+
version: 1,
|
|
1011
|
+
agents: [{ path: "./agents/duration-agent.yaml" }],
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
const manager = new FleetManager({
|
|
1015
|
+
configPath,
|
|
1016
|
+
stateDir,
|
|
1017
|
+
logger: createSilentLogger(),
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
await manager.initialize();
|
|
1021
|
+
|
|
1022
|
+
// Trigger and cancel a job
|
|
1023
|
+
const result = await manager.trigger("duration-agent");
|
|
1024
|
+
await manager.cancelJob(result.jobId);
|
|
1025
|
+
|
|
1026
|
+
// Cancel again (already stopped)
|
|
1027
|
+
const secondCancel = await manager.cancelJob(result.jobId);
|
|
1028
|
+
|
|
1029
|
+
expect(secondCancel.success).toBe(true);
|
|
1030
|
+
expect(secondCancel.terminationType).toBe("already_stopped");
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
// ===========================================================================
|
|
1035
|
+
// forkJob Additional Tests
|
|
1036
|
+
// ===========================================================================
|
|
1037
|
+
describe("forkJob additional tests", () => {
|
|
1038
|
+
it("forks with schedule modification", async () => {
|
|
1039
|
+
await createAgentConfig("fork-schedule-agent", {
|
|
1040
|
+
name: "fork-schedule-agent",
|
|
1041
|
+
schedules: {
|
|
1042
|
+
hourly: { type: "interval", interval: "1h", prompt: "Hourly check" },
|
|
1043
|
+
daily: { type: "interval", interval: "24h", prompt: "Daily check" },
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
const configPath = await createConfig({
|
|
1048
|
+
version: 1,
|
|
1049
|
+
agents: [{ path: "./agents/fork-schedule-agent.yaml" }],
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
const manager = new FleetManager({
|
|
1053
|
+
configPath,
|
|
1054
|
+
stateDir,
|
|
1055
|
+
logger: createSilentLogger(),
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
await manager.initialize();
|
|
1059
|
+
|
|
1060
|
+
// Trigger with hourly schedule
|
|
1061
|
+
const original = await manager.trigger("fork-schedule-agent", "hourly");
|
|
1062
|
+
|
|
1063
|
+
// Fork with different schedule
|
|
1064
|
+
const forked = await manager.forkJob(original.jobId, {
|
|
1065
|
+
schedule: "daily",
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
expect(forked.forkedFromJobId).toBe(original.jobId);
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
it("forks preserving original prompt when no modification", async () => {
|
|
1072
|
+
await createAgentConfig("fork-preserve-agent", {
|
|
1073
|
+
name: "fork-preserve-agent",
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const configPath = await createConfig({
|
|
1077
|
+
version: 1,
|
|
1078
|
+
agents: [{ path: "./agents/fork-preserve-agent.yaml" }],
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
const manager = new FleetManager({
|
|
1082
|
+
configPath,
|
|
1083
|
+
stateDir,
|
|
1084
|
+
logger: createSilentLogger(),
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
await manager.initialize();
|
|
1088
|
+
|
|
1089
|
+
const original = await manager.trigger("fork-preserve-agent", undefined, {
|
|
1090
|
+
prompt: "Original prompt",
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// Fork without modifications
|
|
1094
|
+
const forked = await manager.forkJob(original.jobId);
|
|
1095
|
+
|
|
1096
|
+
expect(forked.prompt).toBe("Original prompt");
|
|
1097
|
+
});
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
// ===========================================================================
|
|
1101
|
+
// getAgents and getConfig tests
|
|
1102
|
+
// ===========================================================================
|
|
1103
|
+
describe("getAgents and getConfig", () => {
|
|
1104
|
+
it("getAgents returns empty array when not initialized", async () => {
|
|
1105
|
+
const configPath = await createConfig({
|
|
1106
|
+
version: 1,
|
|
1107
|
+
agents: [],
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
const manager = new FleetManager({
|
|
1111
|
+
configPath,
|
|
1112
|
+
stateDir,
|
|
1113
|
+
logger: createSilentLogger(),
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
const agents = manager.getAgents();
|
|
1117
|
+
expect(agents).toEqual([]);
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
it("getConfig returns null when not initialized", async () => {
|
|
1121
|
+
const configPath = await createConfig({
|
|
1122
|
+
version: 1,
|
|
1123
|
+
agents: [],
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
const manager = new FleetManager({
|
|
1127
|
+
configPath,
|
|
1128
|
+
stateDir,
|
|
1129
|
+
logger: createSilentLogger(),
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
const config = manager.getConfig();
|
|
1133
|
+
expect(config).toBeNull();
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it("getAgents returns agents after initialization", async () => {
|
|
1137
|
+
await createAgentConfig("get-agent", {
|
|
1138
|
+
name: "get-agent",
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const configPath = await createConfig({
|
|
1142
|
+
version: 1,
|
|
1143
|
+
agents: [{ path: "./agents/get-agent.yaml" }],
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
const manager = new FleetManager({
|
|
1147
|
+
configPath,
|
|
1148
|
+
stateDir,
|
|
1149
|
+
logger: createSilentLogger(),
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
await manager.initialize();
|
|
1153
|
+
|
|
1154
|
+
const agents = manager.getAgents();
|
|
1155
|
+
expect(agents).toHaveLength(1);
|
|
1156
|
+
expect(agents[0].name).toBe("get-agent");
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
// ===========================================================================
|
|
1161
|
+
// Schedule enable/disable edge cases
|
|
1162
|
+
// ===========================================================================
|
|
1163
|
+
describe("Schedule enable/disable edge cases", () => {
|
|
1164
|
+
it("enableSchedule throws AgentNotFoundError for unknown agent", async () => {
|
|
1165
|
+
await createAgentConfig("enable-agent", {
|
|
1166
|
+
name: "enable-agent",
|
|
1167
|
+
schedules: {
|
|
1168
|
+
test: { type: "interval", interval: "1h" },
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
const configPath = await createConfig({
|
|
1173
|
+
version: 1,
|
|
1174
|
+
agents: [{ path: "./agents/enable-agent.yaml" }],
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
const manager = new FleetManager({
|
|
1178
|
+
configPath,
|
|
1179
|
+
stateDir,
|
|
1180
|
+
logger: createSilentLogger(),
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
await manager.initialize();
|
|
1184
|
+
|
|
1185
|
+
await expect(
|
|
1186
|
+
manager.enableSchedule("unknown-agent", "test")
|
|
1187
|
+
).rejects.toThrow(AgentNotFoundError);
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
it("enableSchedule throws ScheduleNotFoundError for unknown schedule", async () => {
|
|
1191
|
+
await createAgentConfig("enable-schedule-agent", {
|
|
1192
|
+
name: "enable-schedule-agent",
|
|
1193
|
+
schedules: {
|
|
1194
|
+
known: { type: "interval", interval: "1h" },
|
|
1195
|
+
},
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
const configPath = await createConfig({
|
|
1199
|
+
version: 1,
|
|
1200
|
+
agents: [{ path: "./agents/enable-schedule-agent.yaml" }],
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
const manager = new FleetManager({
|
|
1204
|
+
configPath,
|
|
1205
|
+
stateDir,
|
|
1206
|
+
logger: createSilentLogger(),
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
await manager.initialize();
|
|
1210
|
+
|
|
1211
|
+
await expect(
|
|
1212
|
+
manager.enableSchedule("enable-schedule-agent", "unknown")
|
|
1213
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
it("disableSchedule throws AgentNotFoundError for unknown agent", async () => {
|
|
1217
|
+
await createAgentConfig("disable-agent", {
|
|
1218
|
+
name: "disable-agent",
|
|
1219
|
+
schedules: {
|
|
1220
|
+
test: { type: "interval", interval: "1h" },
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
const configPath = await createConfig({
|
|
1225
|
+
version: 1,
|
|
1226
|
+
agents: [{ path: "./agents/disable-agent.yaml" }],
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
const manager = new FleetManager({
|
|
1230
|
+
configPath,
|
|
1231
|
+
stateDir,
|
|
1232
|
+
logger: createSilentLogger(),
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
await manager.initialize();
|
|
1236
|
+
|
|
1237
|
+
await expect(
|
|
1238
|
+
manager.disableSchedule("unknown-agent", "test")
|
|
1239
|
+
).rejects.toThrow(AgentNotFoundError);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
it("disableSchedule throws ScheduleNotFoundError for unknown schedule", async () => {
|
|
1243
|
+
await createAgentConfig("disable-schedule-agent", {
|
|
1244
|
+
name: "disable-schedule-agent",
|
|
1245
|
+
schedules: {
|
|
1246
|
+
known: { type: "interval", interval: "1h" },
|
|
1247
|
+
},
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
const configPath = await createConfig({
|
|
1251
|
+
version: 1,
|
|
1252
|
+
agents: [{ path: "./agents/disable-schedule-agent.yaml" }],
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
const manager = new FleetManager({
|
|
1256
|
+
configPath,
|
|
1257
|
+
stateDir,
|
|
1258
|
+
logger: createSilentLogger(),
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
await manager.initialize();
|
|
1262
|
+
|
|
1263
|
+
await expect(
|
|
1264
|
+
manager.disableSchedule("disable-schedule-agent", "unknown")
|
|
1265
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it("enableSchedule for agent without schedules throws ScheduleNotFoundError", async () => {
|
|
1269
|
+
await createAgentConfig("no-schedule-enable", {
|
|
1270
|
+
name: "no-schedule-enable",
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
const configPath = await createConfig({
|
|
1274
|
+
version: 1,
|
|
1275
|
+
agents: [{ path: "./agents/no-schedule-enable.yaml" }],
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
const manager = new FleetManager({
|
|
1279
|
+
configPath,
|
|
1280
|
+
stateDir,
|
|
1281
|
+
logger: createSilentLogger(),
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
await manager.initialize();
|
|
1285
|
+
|
|
1286
|
+
await expect(
|
|
1287
|
+
manager.enableSchedule("no-schedule-enable", "any")
|
|
1288
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
it("disableSchedule for agent without schedules throws ScheduleNotFoundError", async () => {
|
|
1292
|
+
await createAgentConfig("no-schedule-disable", {
|
|
1293
|
+
name: "no-schedule-disable",
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
const configPath = await createConfig({
|
|
1297
|
+
version: 1,
|
|
1298
|
+
agents: [{ path: "./agents/no-schedule-disable.yaml" }],
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
const manager = new FleetManager({
|
|
1302
|
+
configPath,
|
|
1303
|
+
stateDir,
|
|
1304
|
+
logger: createSilentLogger(),
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
await manager.initialize();
|
|
1308
|
+
|
|
1309
|
+
await expect(
|
|
1310
|
+
manager.disableSchedule("no-schedule-disable", "any")
|
|
1311
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
1312
|
+
});
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
// ===========================================================================
|
|
1316
|
+
// persistShutdownState edge cases
|
|
1317
|
+
// ===========================================================================
|
|
1318
|
+
describe("persistShutdownState", () => {
|
|
1319
|
+
it("handles stop when stateDir not initialized", async () => {
|
|
1320
|
+
const configPath = await createConfig({
|
|
1321
|
+
version: 1,
|
|
1322
|
+
agents: [],
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
const manager = new FleetManager({
|
|
1326
|
+
configPath,
|
|
1327
|
+
stateDir,
|
|
1328
|
+
logger: createSilentLogger(),
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
// Stop without initializing - should be a no-op
|
|
1332
|
+
await manager.stop();
|
|
1333
|
+
expect(manager.state.status).toBe("uninitialized");
|
|
1334
|
+
});
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
// ===========================================================================
|
|
1338
|
+
// cancelRunningJobs edge case
|
|
1339
|
+
// ===========================================================================
|
|
1340
|
+
describe("cancelRunningJobs", () => {
|
|
1341
|
+
it("handles case with no running jobs", async () => {
|
|
1342
|
+
await createAgentConfig("no-running-agent", {
|
|
1343
|
+
name: "no-running-agent",
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
const configPath = await createConfig({
|
|
1347
|
+
version: 1,
|
|
1348
|
+
agents: [{ path: "./agents/no-running-agent.yaml" }],
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
const logger = createSilentLogger();
|
|
1352
|
+
const manager = new FleetManager({
|
|
1353
|
+
configPath,
|
|
1354
|
+
stateDir,
|
|
1355
|
+
checkInterval: 10000,
|
|
1356
|
+
logger,
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
await manager.initialize();
|
|
1360
|
+
await manager.start();
|
|
1361
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1362
|
+
|
|
1363
|
+
// Stop with cancelOnTimeout - but no jobs are running
|
|
1364
|
+
await manager.stop({
|
|
1365
|
+
timeout: 100,
|
|
1366
|
+
cancelOnTimeout: true,
|
|
1367
|
+
cancelTimeout: 50,
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
// Should have logged "No running jobs to cancel"
|
|
1371
|
+
expect(logger.debug).toHaveBeenCalled();
|
|
1372
|
+
});
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
// ===========================================================================
|
|
1376
|
+
// Additional trigger tests for coverage
|
|
1377
|
+
// ===========================================================================
|
|
1378
|
+
describe("Trigger edge cases", () => {
|
|
1379
|
+
it("trigger with bypassConcurrencyLimit option", async () => {
|
|
1380
|
+
await createAgentConfig("bypass-agent", {
|
|
1381
|
+
name: "bypass-agent",
|
|
1382
|
+
instances: { max_concurrent: 1 },
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
const configPath = await createConfig({
|
|
1386
|
+
version: 1,
|
|
1387
|
+
agents: [{ path: "./agents/bypass-agent.yaml" }],
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
const manager = new FleetManager({
|
|
1391
|
+
configPath,
|
|
1392
|
+
stateDir,
|
|
1393
|
+
logger: createSilentLogger(),
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
await manager.initialize();
|
|
1397
|
+
|
|
1398
|
+
// Trigger with bypass option - should work even if at capacity
|
|
1399
|
+
const result = await manager.trigger("bypass-agent", undefined, {
|
|
1400
|
+
bypassConcurrencyLimit: true,
|
|
1401
|
+
prompt: "Test prompt",
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
expect(result.agentName).toBe("bypass-agent");
|
|
1405
|
+
expect(result.prompt).toBe("Test prompt");
|
|
1406
|
+
});
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
// ===========================================================================
|
|
1410
|
+
// Additional schedule tests for coverage
|
|
1411
|
+
// ===========================================================================
|
|
1412
|
+
describe("Schedule state file edge cases", () => {
|
|
1413
|
+
it("getSchedules returns empty array when no agents have schedules", async () => {
|
|
1414
|
+
await createAgentConfig("no-schedule-agent", {
|
|
1415
|
+
name: "no-schedule-agent",
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
const configPath = await createConfig({
|
|
1419
|
+
version: 1,
|
|
1420
|
+
agents: [{ path: "./agents/no-schedule-agent.yaml" }],
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
const manager = new FleetManager({
|
|
1424
|
+
configPath,
|
|
1425
|
+
stateDir,
|
|
1426
|
+
logger: createSilentLogger(),
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
await manager.initialize();
|
|
1430
|
+
|
|
1431
|
+
const schedules = await manager.getSchedules();
|
|
1432
|
+
expect(schedules).toEqual([]);
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
// ===========================================================================
|
|
1437
|
+
// Additional config change tests for coverage
|
|
1438
|
+
// ===========================================================================
|
|
1439
|
+
describe("Additional config change detection", () => {
|
|
1440
|
+
it("detects model changes", async () => {
|
|
1441
|
+
await createAgentConfig("model-agent", {
|
|
1442
|
+
name: "model-agent",
|
|
1443
|
+
model: "claude-3-sonnet",
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
const configPath = await createConfig({
|
|
1447
|
+
version: 1,
|
|
1448
|
+
agents: [{ path: "./agents/model-agent.yaml" }],
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
const manager = new FleetManager({
|
|
1452
|
+
configPath,
|
|
1453
|
+
stateDir,
|
|
1454
|
+
logger: createSilentLogger(),
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
await manager.initialize();
|
|
1458
|
+
|
|
1459
|
+
// Modify model
|
|
1460
|
+
await createAgentConfig("model-agent", {
|
|
1461
|
+
name: "model-agent",
|
|
1462
|
+
model: "claude-3-opus",
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
const result = await manager.reload();
|
|
1466
|
+
|
|
1467
|
+
expect(result.changes).toContainEqual(
|
|
1468
|
+
expect.objectContaining({
|
|
1469
|
+
type: "modified",
|
|
1470
|
+
category: "agent",
|
|
1471
|
+
name: "model-agent",
|
|
1472
|
+
details: expect.stringContaining("model"),
|
|
1473
|
+
})
|
|
1474
|
+
);
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
it("detects description changes", async () => {
|
|
1478
|
+
await createAgentConfig("desc-agent", {
|
|
1479
|
+
name: "desc-agent",
|
|
1480
|
+
description: "Original description",
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
const configPath = await createConfig({
|
|
1484
|
+
version: 1,
|
|
1485
|
+
agents: [{ path: "./agents/desc-agent.yaml" }],
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
const manager = new FleetManager({
|
|
1489
|
+
configPath,
|
|
1490
|
+
stateDir,
|
|
1491
|
+
logger: createSilentLogger(),
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
await manager.initialize();
|
|
1495
|
+
|
|
1496
|
+
// Modify description
|
|
1497
|
+
await createAgentConfig("desc-agent", {
|
|
1498
|
+
name: "desc-agent",
|
|
1499
|
+
description: "Updated description",
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
const result = await manager.reload();
|
|
1503
|
+
|
|
1504
|
+
expect(result.changes).toContainEqual(
|
|
1505
|
+
expect.objectContaining({
|
|
1506
|
+
type: "modified",
|
|
1507
|
+
category: "agent",
|
|
1508
|
+
name: "desc-agent",
|
|
1509
|
+
details: expect.stringContaining("description"),
|
|
1510
|
+
})
|
|
1511
|
+
);
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
it("detects schedule prompt changes", async () => {
|
|
1515
|
+
await createAgentConfig("prompt-schedule-agent", {
|
|
1516
|
+
name: "prompt-schedule-agent",
|
|
1517
|
+
schedules: {
|
|
1518
|
+
check: {
|
|
1519
|
+
type: "interval",
|
|
1520
|
+
interval: "1h",
|
|
1521
|
+
prompt: "Original prompt",
|
|
1522
|
+
},
|
|
1523
|
+
},
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
const configPath = await createConfig({
|
|
1527
|
+
version: 1,
|
|
1528
|
+
agents: [{ path: "./agents/prompt-schedule-agent.yaml" }],
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
const manager = new FleetManager({
|
|
1532
|
+
configPath,
|
|
1533
|
+
stateDir,
|
|
1534
|
+
logger: createSilentLogger(),
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
await manager.initialize();
|
|
1538
|
+
|
|
1539
|
+
// Modify schedule prompt
|
|
1540
|
+
await createAgentConfig("prompt-schedule-agent", {
|
|
1541
|
+
name: "prompt-schedule-agent",
|
|
1542
|
+
schedules: {
|
|
1543
|
+
check: {
|
|
1544
|
+
type: "interval",
|
|
1545
|
+
interval: "1h",
|
|
1546
|
+
prompt: "Updated prompt",
|
|
1547
|
+
},
|
|
1548
|
+
},
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
const result = await manager.reload();
|
|
1552
|
+
|
|
1553
|
+
expect(result.changes).toContainEqual(
|
|
1554
|
+
expect.objectContaining({
|
|
1555
|
+
type: "modified",
|
|
1556
|
+
category: "schedule",
|
|
1557
|
+
name: "prompt-schedule-agent/check",
|
|
1558
|
+
details: expect.stringContaining("prompt"),
|
|
1559
|
+
})
|
|
1560
|
+
);
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
it("detects schedule interval changes", async () => {
|
|
1564
|
+
await createAgentConfig("interval-schedule-agent", {
|
|
1565
|
+
name: "interval-schedule-agent",
|
|
1566
|
+
schedules: {
|
|
1567
|
+
check: {
|
|
1568
|
+
type: "interval",
|
|
1569
|
+
interval: "1h",
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
const configPath = await createConfig({
|
|
1575
|
+
version: 1,
|
|
1576
|
+
agents: [{ path: "./agents/interval-schedule-agent.yaml" }],
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
const manager = new FleetManager({
|
|
1580
|
+
configPath,
|
|
1581
|
+
stateDir,
|
|
1582
|
+
logger: createSilentLogger(),
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
await manager.initialize();
|
|
1586
|
+
|
|
1587
|
+
// Modify interval
|
|
1588
|
+
await createAgentConfig("interval-schedule-agent", {
|
|
1589
|
+
name: "interval-schedule-agent",
|
|
1590
|
+
schedules: {
|
|
1591
|
+
check: {
|
|
1592
|
+
type: "interval",
|
|
1593
|
+
interval: "2h",
|
|
1594
|
+
},
|
|
1595
|
+
},
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
const result = await manager.reload();
|
|
1599
|
+
|
|
1600
|
+
expect(result.changes).toContainEqual(
|
|
1601
|
+
expect.objectContaining({
|
|
1602
|
+
type: "modified",
|
|
1603
|
+
category: "schedule",
|
|
1604
|
+
name: "interval-schedule-agent/check",
|
|
1605
|
+
details: expect.stringContaining("interval"),
|
|
1606
|
+
})
|
|
1607
|
+
);
|
|
1608
|
+
});
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
// ===========================================================================
|
|
1612
|
+
// Event emission tests
|
|
1613
|
+
// ===========================================================================
|
|
1614
|
+
describe("Event emission tests", () => {
|
|
1615
|
+
it("emits initialized event", async () => {
|
|
1616
|
+
await createAgentConfig("event-init-agent", {
|
|
1617
|
+
name: "event-init-agent",
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
const configPath = await createConfig({
|
|
1621
|
+
version: 1,
|
|
1622
|
+
agents: [{ path: "./agents/event-init-agent.yaml" }],
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
const manager = new FleetManager({
|
|
1626
|
+
configPath,
|
|
1627
|
+
stateDir,
|
|
1628
|
+
logger: createSilentLogger(),
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
const initHandler = vi.fn();
|
|
1632
|
+
manager.on("initialized", initHandler);
|
|
1633
|
+
|
|
1634
|
+
await manager.initialize();
|
|
1635
|
+
|
|
1636
|
+
expect(initHandler).toHaveBeenCalledTimes(1);
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
it("emits error event on initialization failure", async () => {
|
|
1640
|
+
// Create invalid config
|
|
1641
|
+
const configPath = await createConfig({
|
|
1642
|
+
version: 1,
|
|
1643
|
+
agents: [{ path: "./agents/nonexistent.yaml" }],
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
const manager = new FleetManager({
|
|
1647
|
+
configPath,
|
|
1648
|
+
stateDir,
|
|
1649
|
+
logger: createSilentLogger(),
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
const errorHandler = vi.fn();
|
|
1653
|
+
manager.on("error", errorHandler);
|
|
1654
|
+
|
|
1655
|
+
try {
|
|
1656
|
+
await manager.initialize();
|
|
1657
|
+
} catch {
|
|
1658
|
+
// Expected
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
expect(errorHandler).toHaveBeenCalledTimes(1);
|
|
1662
|
+
});
|
|
1663
|
+
});
|
|
1664
|
+
|
|
1665
|
+
// ===========================================================================
|
|
1666
|
+
// streamAgentLogs tests
|
|
1667
|
+
// ===========================================================================
|
|
1668
|
+
describe("streamAgentLogs", () => {
|
|
1669
|
+
it("throws AgentNotFoundError for unknown agent", async () => {
|
|
1670
|
+
await createAgentConfig("known-agent", {
|
|
1671
|
+
name: "known-agent",
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
const configPath = await createConfig({
|
|
1675
|
+
version: 1,
|
|
1676
|
+
agents: [{ path: "./agents/known-agent.yaml" }],
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
const manager = new FleetManager({
|
|
1680
|
+
configPath,
|
|
1681
|
+
stateDir,
|
|
1682
|
+
logger: createSilentLogger(),
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
await manager.initialize();
|
|
1686
|
+
|
|
1687
|
+
const stream = manager.streamAgentLogs("unknown-agent");
|
|
1688
|
+
// Get the iterator and call next to trigger the check
|
|
1689
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
1690
|
+
await expect(iterator.next()).rejects.toThrow(AgentNotFoundError);
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
it("returns async iterable for valid agent", async () => {
|
|
1694
|
+
await createAgentConfig("stream-log-agent", {
|
|
1695
|
+
name: "stream-log-agent",
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
const configPath = await createConfig({
|
|
1699
|
+
version: 1,
|
|
1700
|
+
agents: [{ path: "./agents/stream-log-agent.yaml" }],
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
const manager = new FleetManager({
|
|
1704
|
+
configPath,
|
|
1705
|
+
stateDir,
|
|
1706
|
+
logger: createSilentLogger(),
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
await manager.initialize();
|
|
1710
|
+
|
|
1711
|
+
const stream = manager.streamAgentLogs("stream-log-agent");
|
|
1712
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
1713
|
+
});
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
// ===========================================================================
|
|
1717
|
+
// getAgentInfo tests
|
|
1718
|
+
// ===========================================================================
|
|
1719
|
+
describe("getAgentInfo", () => {
|
|
1720
|
+
it("returns agent info before initialization", async () => {
|
|
1721
|
+
const configPath = await createConfig({
|
|
1722
|
+
version: 1,
|
|
1723
|
+
agents: [],
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
const manager = new FleetManager({
|
|
1727
|
+
configPath,
|
|
1728
|
+
stateDir,
|
|
1729
|
+
logger: createSilentLogger(),
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
// Before initialization, should return empty array
|
|
1733
|
+
const agents = await manager.getAgentInfo();
|
|
1734
|
+
expect(agents).toEqual([]);
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
it("returns agent info with all fields", async () => {
|
|
1738
|
+
await createAgentConfig("full-agent", {
|
|
1739
|
+
name: "full-agent",
|
|
1740
|
+
description: "Full test agent",
|
|
1741
|
+
model: "claude-3",
|
|
1742
|
+
workspace: "/path/to/workspace",
|
|
1743
|
+
instances: { max_concurrent: 3 },
|
|
1744
|
+
schedules: {
|
|
1745
|
+
hourly: { type: "interval", interval: "1h" },
|
|
1746
|
+
},
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
const configPath = await createConfig({
|
|
1750
|
+
version: 1,
|
|
1751
|
+
agents: [{ path: "./agents/full-agent.yaml" }],
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
const manager = new FleetManager({
|
|
1755
|
+
configPath,
|
|
1756
|
+
stateDir,
|
|
1757
|
+
logger: createSilentLogger(),
|
|
1758
|
+
});
|
|
1759
|
+
|
|
1760
|
+
await manager.initialize();
|
|
1761
|
+
|
|
1762
|
+
const agents = await manager.getAgentInfo();
|
|
1763
|
+
expect(agents).toHaveLength(1);
|
|
1764
|
+
|
|
1765
|
+
const agent = agents[0];
|
|
1766
|
+
expect(agent.name).toBe("full-agent");
|
|
1767
|
+
expect(agent.description).toBe("Full test agent");
|
|
1768
|
+
expect(agent.model).toBe("claude-3");
|
|
1769
|
+
expect(agent.workspace).toBe("/path/to/workspace");
|
|
1770
|
+
expect(agent.maxConcurrent).toBe(3);
|
|
1771
|
+
expect(agent.scheduleCount).toBe(1);
|
|
1772
|
+
expect(agent.schedules).toHaveLength(1);
|
|
1773
|
+
expect(agent.schedules[0].name).toBe("hourly");
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
it("returns agent info with workspace object", async () => {
|
|
1777
|
+
await createAgentConfig("workspace-obj-agent", {
|
|
1778
|
+
name: "workspace-obj-agent",
|
|
1779
|
+
workspace: {
|
|
1780
|
+
root: "/object/workspace/path",
|
|
1781
|
+
},
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
const configPath = await createConfig({
|
|
1785
|
+
version: 1,
|
|
1786
|
+
agents: [{ path: "./agents/workspace-obj-agent.yaml" }],
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
const manager = new FleetManager({
|
|
1790
|
+
configPath,
|
|
1791
|
+
stateDir,
|
|
1792
|
+
logger: createSilentLogger(),
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
await manager.initialize();
|
|
1796
|
+
|
|
1797
|
+
const agents = await manager.getAgentInfo();
|
|
1798
|
+
expect(agents[0].workspace).toBe("/object/workspace/path");
|
|
1799
|
+
});
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
// ===========================================================================
|
|
1803
|
+
// getFleetStatus tests
|
|
1804
|
+
// ===========================================================================
|
|
1805
|
+
describe("getFleetStatus", () => {
|
|
1806
|
+
it("returns scheduler status when not initialized", async () => {
|
|
1807
|
+
const configPath = await createConfig({
|
|
1808
|
+
version: 1,
|
|
1809
|
+
agents: [],
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
const manager = new FleetManager({
|
|
1813
|
+
configPath,
|
|
1814
|
+
stateDir,
|
|
1815
|
+
logger: createSilentLogger(),
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
const status = await manager.getFleetStatus();
|
|
1819
|
+
expect(status.scheduler.status).toBe("stopped");
|
|
1820
|
+
});
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
// ===========================================================================
|
|
1824
|
+
// Multiple workspace format tests
|
|
1825
|
+
// ===========================================================================
|
|
1826
|
+
describe("Workspace handling", () => {
|
|
1827
|
+
it("handles agent with no workspace", async () => {
|
|
1828
|
+
await createAgentConfig("no-workspace-agent", {
|
|
1829
|
+
name: "no-workspace-agent",
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
const configPath = await createConfig({
|
|
1833
|
+
version: 1,
|
|
1834
|
+
agents: [{ path: "./agents/no-workspace-agent.yaml" }],
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
const manager = new FleetManager({
|
|
1838
|
+
configPath,
|
|
1839
|
+
stateDir,
|
|
1840
|
+
logger: createSilentLogger(),
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
await manager.initialize();
|
|
1844
|
+
|
|
1845
|
+
const agents = await manager.getAgentInfo();
|
|
1846
|
+
expect(agents[0].workspace).toBeUndefined();
|
|
1847
|
+
});
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
// ===========================================================================
|
|
1851
|
+
// Stop error handling
|
|
1852
|
+
// ===========================================================================
|
|
1853
|
+
describe("Stop error handling", () => {
|
|
1854
|
+
it("handles stop when status is stopping", async () => {
|
|
1855
|
+
await createAgentConfig("stopping-agent", {
|
|
1856
|
+
name: "stopping-agent",
|
|
1857
|
+
});
|
|
1858
|
+
|
|
1859
|
+
const configPath = await createConfig({
|
|
1860
|
+
version: 1,
|
|
1861
|
+
agents: [{ path: "./agents/stopping-agent.yaml" }],
|
|
1862
|
+
});
|
|
1863
|
+
|
|
1864
|
+
const manager = new FleetManager({
|
|
1865
|
+
configPath,
|
|
1866
|
+
stateDir,
|
|
1867
|
+
checkInterval: 10000,
|
|
1868
|
+
logger: createSilentLogger(),
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1871
|
+
await manager.initialize();
|
|
1872
|
+
await manager.start();
|
|
1873
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1874
|
+
|
|
1875
|
+
// Call stop which will complete
|
|
1876
|
+
await manager.stop();
|
|
1877
|
+
|
|
1878
|
+
// Second stop should be no-op since status is 'stopped'
|
|
1879
|
+
await manager.stop();
|
|
1880
|
+
expect(manager.state.status).toBe("stopped");
|
|
1881
|
+
});
|
|
1882
|
+
});
|
|
1883
|
+
|
|
1884
|
+
// ===========================================================================
|
|
1885
|
+
// FleetManagerStateError tests
|
|
1886
|
+
// ===========================================================================
|
|
1887
|
+
describe("FleetManagerStateError", () => {
|
|
1888
|
+
it("has requiredState alias for backwards compatibility", async () => {
|
|
1889
|
+
const { FleetManagerStateError } = await import("../errors.js");
|
|
1890
|
+
const error = new FleetManagerStateError("test", "current", "required");
|
|
1891
|
+
expect(error.requiredState).toBe("required");
|
|
1892
|
+
});
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
// ===========================================================================
|
|
1896
|
+
// Event emission helper methods
|
|
1897
|
+
// ===========================================================================
|
|
1898
|
+
describe("Event emission helpers", () => {
|
|
1899
|
+
it("emitConfigReloaded emits config:reloaded event", async () => {
|
|
1900
|
+
await createAgentConfig("emit-config-agent", {
|
|
1901
|
+
name: "emit-config-agent",
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
const configPath = await createConfig({
|
|
1905
|
+
version: 1,
|
|
1906
|
+
agents: [{ path: "./agents/emit-config-agent.yaml" }],
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
const manager = new FleetManager({
|
|
1910
|
+
configPath,
|
|
1911
|
+
stateDir,
|
|
1912
|
+
logger: createSilentLogger(),
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
await manager.initialize();
|
|
1916
|
+
|
|
1917
|
+
const handler = vi.fn();
|
|
1918
|
+
manager.on("config:reloaded", handler);
|
|
1919
|
+
|
|
1920
|
+
manager.emitConfigReloaded({
|
|
1921
|
+
agentCount: 1,
|
|
1922
|
+
agentNames: ["emit-config-agent"],
|
|
1923
|
+
configPath,
|
|
1924
|
+
changes: [],
|
|
1925
|
+
timestamp: new Date().toISOString(),
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
it("emitAgentStarted emits agent:started event", async () => {
|
|
1932
|
+
await createAgentConfig("emit-start-agent", {
|
|
1933
|
+
name: "emit-start-agent",
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1936
|
+
const configPath = await createConfig({
|
|
1937
|
+
version: 1,
|
|
1938
|
+
agents: [{ path: "./agents/emit-start-agent.yaml" }],
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
const manager = new FleetManager({
|
|
1942
|
+
configPath,
|
|
1943
|
+
stateDir,
|
|
1944
|
+
logger: createSilentLogger(),
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
await manager.initialize();
|
|
1948
|
+
|
|
1949
|
+
const handler = vi.fn();
|
|
1950
|
+
manager.on("agent:started", handler);
|
|
1951
|
+
|
|
1952
|
+
const agent = manager.getAgents().find(a => a.name === "emit-start-agent")!;
|
|
1953
|
+
manager.emitAgentStarted({
|
|
1954
|
+
agent,
|
|
1955
|
+
timestamp: new Date().toISOString(),
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
1959
|
+
});
|
|
1960
|
+
|
|
1961
|
+
it("emitAgentStopped emits agent:stopped event", async () => {
|
|
1962
|
+
await createAgentConfig("emit-stop-agent", {
|
|
1963
|
+
name: "emit-stop-agent",
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
const configPath = await createConfig({
|
|
1967
|
+
version: 1,
|
|
1968
|
+
agents: [{ path: "./agents/emit-stop-agent.yaml" }],
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
const manager = new FleetManager({
|
|
1972
|
+
configPath,
|
|
1973
|
+
stateDir,
|
|
1974
|
+
logger: createSilentLogger(),
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
await manager.initialize();
|
|
1978
|
+
|
|
1979
|
+
const handler = vi.fn();
|
|
1980
|
+
manager.on("agent:stopped", handler);
|
|
1981
|
+
|
|
1982
|
+
manager.emitAgentStopped({
|
|
1983
|
+
agentName: "emit-stop-agent",
|
|
1984
|
+
reason: "test",
|
|
1985
|
+
timestamp: new Date().toISOString(),
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
it("emitScheduleSkipped emits schedule:skipped event", async () => {
|
|
1992
|
+
await createAgentConfig("emit-skip-agent", {
|
|
1993
|
+
name: "emit-skip-agent",
|
|
1994
|
+
schedules: {
|
|
1995
|
+
test: { type: "interval", interval: "1h" },
|
|
1996
|
+
},
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
const configPath = await createConfig({
|
|
2000
|
+
version: 1,
|
|
2001
|
+
agents: [{ path: "./agents/emit-skip-agent.yaml" }],
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
const manager = new FleetManager({
|
|
2005
|
+
configPath,
|
|
2006
|
+
stateDir,
|
|
2007
|
+
logger: createSilentLogger(),
|
|
2008
|
+
});
|
|
2009
|
+
|
|
2010
|
+
await manager.initialize();
|
|
2011
|
+
|
|
2012
|
+
const handler = vi.fn();
|
|
2013
|
+
manager.on("schedule:skipped", handler);
|
|
2014
|
+
|
|
2015
|
+
manager.emitScheduleSkipped({
|
|
2016
|
+
agentName: "emit-skip-agent",
|
|
2017
|
+
scheduleName: "test",
|
|
2018
|
+
reason: "max_concurrent",
|
|
2019
|
+
timestamp: new Date().toISOString(),
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2023
|
+
});
|
|
2024
|
+
|
|
2025
|
+
it("emitJobCreated emits job:created event", async () => {
|
|
2026
|
+
await createAgentConfig("emit-job-create-agent", {
|
|
2027
|
+
name: "emit-job-create-agent",
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
const configPath = await createConfig({
|
|
2031
|
+
version: 1,
|
|
2032
|
+
agents: [{ path: "./agents/emit-job-create-agent.yaml" }],
|
|
2033
|
+
});
|
|
2034
|
+
|
|
2035
|
+
const manager = new FleetManager({
|
|
2036
|
+
configPath,
|
|
2037
|
+
stateDir,
|
|
2038
|
+
logger: createSilentLogger(),
|
|
2039
|
+
});
|
|
2040
|
+
|
|
2041
|
+
await manager.initialize();
|
|
2042
|
+
|
|
2043
|
+
const handler = vi.fn();
|
|
2044
|
+
manager.on("job:created", handler);
|
|
2045
|
+
|
|
2046
|
+
manager.emitJobCreated({
|
|
2047
|
+
job: {
|
|
2048
|
+
id: "test-job-id",
|
|
2049
|
+
agent: "emit-job-create-agent",
|
|
2050
|
+
trigger_type: "manual",
|
|
2051
|
+
status: "pending",
|
|
2052
|
+
started_at: new Date().toISOString(),
|
|
2053
|
+
schedule: null,
|
|
2054
|
+
prompt: null,
|
|
2055
|
+
forked_from: null,
|
|
2056
|
+
},
|
|
2057
|
+
agentName: "emit-job-create-agent",
|
|
2058
|
+
timestamp: new Date().toISOString(),
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
it("emitJobOutput emits job:output event", async () => {
|
|
2065
|
+
await createAgentConfig("emit-output-agent", {
|
|
2066
|
+
name: "emit-output-agent",
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
const configPath = await createConfig({
|
|
2070
|
+
version: 1,
|
|
2071
|
+
agents: [{ path: "./agents/emit-output-agent.yaml" }],
|
|
2072
|
+
});
|
|
2073
|
+
|
|
2074
|
+
const manager = new FleetManager({
|
|
2075
|
+
configPath,
|
|
2076
|
+
stateDir,
|
|
2077
|
+
logger: createSilentLogger(),
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
await manager.initialize();
|
|
2081
|
+
|
|
2082
|
+
const handler = vi.fn();
|
|
2083
|
+
manager.on("job:output", handler);
|
|
2084
|
+
|
|
2085
|
+
manager.emitJobOutput({
|
|
2086
|
+
jobId: "test-job-id",
|
|
2087
|
+
agentName: "emit-output-agent",
|
|
2088
|
+
output: "test output",
|
|
2089
|
+
outputType: "stdout",
|
|
2090
|
+
timestamp: new Date().toISOString(),
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
it("emitJobCompleted emits job:completed event", async () => {
|
|
2097
|
+
await createAgentConfig("emit-complete-agent", {
|
|
2098
|
+
name: "emit-complete-agent",
|
|
2099
|
+
});
|
|
2100
|
+
|
|
2101
|
+
const configPath = await createConfig({
|
|
2102
|
+
version: 1,
|
|
2103
|
+
agents: [{ path: "./agents/emit-complete-agent.yaml" }],
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
const manager = new FleetManager({
|
|
2107
|
+
configPath,
|
|
2108
|
+
stateDir,
|
|
2109
|
+
logger: createSilentLogger(),
|
|
2110
|
+
});
|
|
2111
|
+
|
|
2112
|
+
await manager.initialize();
|
|
2113
|
+
|
|
2114
|
+
const handler = vi.fn();
|
|
2115
|
+
manager.on("job:completed", handler);
|
|
2116
|
+
|
|
2117
|
+
manager.emitJobCompleted({
|
|
2118
|
+
job: {
|
|
2119
|
+
id: "test-job-id",
|
|
2120
|
+
agent: "emit-complete-agent",
|
|
2121
|
+
trigger_type: "manual",
|
|
2122
|
+
status: "completed",
|
|
2123
|
+
started_at: new Date().toISOString(),
|
|
2124
|
+
schedule: null,
|
|
2125
|
+
prompt: null,
|
|
2126
|
+
forked_from: null,
|
|
2127
|
+
},
|
|
2128
|
+
agentName: "emit-complete-agent",
|
|
2129
|
+
exitReason: "success",
|
|
2130
|
+
durationSeconds: 10,
|
|
2131
|
+
timestamp: new Date().toISOString(),
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
it("emitJobFailed emits job:failed event", async () => {
|
|
2138
|
+
await createAgentConfig("emit-fail-agent", {
|
|
2139
|
+
name: "emit-fail-agent",
|
|
2140
|
+
});
|
|
2141
|
+
|
|
2142
|
+
const configPath = await createConfig({
|
|
2143
|
+
version: 1,
|
|
2144
|
+
agents: [{ path: "./agents/emit-fail-agent.yaml" }],
|
|
2145
|
+
});
|
|
2146
|
+
|
|
2147
|
+
const manager = new FleetManager({
|
|
2148
|
+
configPath,
|
|
2149
|
+
stateDir,
|
|
2150
|
+
logger: createSilentLogger(),
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
await manager.initialize();
|
|
2154
|
+
|
|
2155
|
+
const handler = vi.fn();
|
|
2156
|
+
manager.on("job:failed", handler);
|
|
2157
|
+
|
|
2158
|
+
manager.emitJobFailed({
|
|
2159
|
+
job: {
|
|
2160
|
+
id: "test-job-id",
|
|
2161
|
+
agent: "emit-fail-agent",
|
|
2162
|
+
trigger_type: "manual",
|
|
2163
|
+
status: "failed",
|
|
2164
|
+
started_at: new Date().toISOString(),
|
|
2165
|
+
schedule: null,
|
|
2166
|
+
prompt: null,
|
|
2167
|
+
forked_from: null,
|
|
2168
|
+
},
|
|
2169
|
+
agentName: "emit-fail-agent",
|
|
2170
|
+
error: new Error("Test error"),
|
|
2171
|
+
exitReason: "error",
|
|
2172
|
+
timestamp: new Date().toISOString(),
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
it("emitJobCancelled emits job:cancelled event", async () => {
|
|
2179
|
+
await createAgentConfig("emit-cancel-agent", {
|
|
2180
|
+
name: "emit-cancel-agent",
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
const configPath = await createConfig({
|
|
2184
|
+
version: 1,
|
|
2185
|
+
agents: [{ path: "./agents/emit-cancel-agent.yaml" }],
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2188
|
+
const manager = new FleetManager({
|
|
2189
|
+
configPath,
|
|
2190
|
+
stateDir,
|
|
2191
|
+
logger: createSilentLogger(),
|
|
2192
|
+
});
|
|
2193
|
+
|
|
2194
|
+
await manager.initialize();
|
|
2195
|
+
|
|
2196
|
+
const handler = vi.fn();
|
|
2197
|
+
manager.on("job:cancelled", handler);
|
|
2198
|
+
|
|
2199
|
+
manager.emitJobCancelled({
|
|
2200
|
+
job: {
|
|
2201
|
+
id: "test-job-id",
|
|
2202
|
+
agent: "emit-cancel-agent",
|
|
2203
|
+
trigger_type: "manual",
|
|
2204
|
+
status: "cancelled",
|
|
2205
|
+
started_at: new Date().toISOString(),
|
|
2206
|
+
schedule: null,
|
|
2207
|
+
prompt: null,
|
|
2208
|
+
forked_from: null,
|
|
2209
|
+
},
|
|
2210
|
+
agentName: "emit-cancel-agent",
|
|
2211
|
+
terminationType: "graceful",
|
|
2212
|
+
timestamp: new Date().toISOString(),
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
it("emitJobForked emits job:forked event", async () => {
|
|
2219
|
+
await createAgentConfig("emit-fork-agent", {
|
|
2220
|
+
name: "emit-fork-agent",
|
|
2221
|
+
});
|
|
2222
|
+
|
|
2223
|
+
const configPath = await createConfig({
|
|
2224
|
+
version: 1,
|
|
2225
|
+
agents: [{ path: "./agents/emit-fork-agent.yaml" }],
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
const manager = new FleetManager({
|
|
2229
|
+
configPath,
|
|
2230
|
+
stateDir,
|
|
2231
|
+
logger: createSilentLogger(),
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
await manager.initialize();
|
|
2235
|
+
|
|
2236
|
+
const handler = vi.fn();
|
|
2237
|
+
manager.on("job:forked", handler);
|
|
2238
|
+
|
|
2239
|
+
const originalJob = {
|
|
2240
|
+
id: "original-job-id",
|
|
2241
|
+
agent: "emit-fork-agent",
|
|
2242
|
+
trigger_type: "manual" as const,
|
|
2243
|
+
status: "completed" as const,
|
|
2244
|
+
started_at: new Date().toISOString(),
|
|
2245
|
+
schedule: null,
|
|
2246
|
+
prompt: null,
|
|
2247
|
+
forked_from: null,
|
|
2248
|
+
};
|
|
2249
|
+
|
|
2250
|
+
manager.emitJobForked({
|
|
2251
|
+
job: {
|
|
2252
|
+
id: "forked-job-id",
|
|
2253
|
+
agent: "emit-fork-agent",
|
|
2254
|
+
trigger_type: "fork",
|
|
2255
|
+
status: "pending",
|
|
2256
|
+
started_at: new Date().toISOString(),
|
|
2257
|
+
schedule: null,
|
|
2258
|
+
prompt: null,
|
|
2259
|
+
forked_from: "original-job-id",
|
|
2260
|
+
},
|
|
2261
|
+
originalJob,
|
|
2262
|
+
agentName: "emit-fork-agent",
|
|
2263
|
+
timestamp: new Date().toISOString(),
|
|
2264
|
+
});
|
|
2265
|
+
|
|
2266
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
2267
|
+
});
|
|
2268
|
+
});
|
|
2269
|
+
|
|
2270
|
+
// ===========================================================================
|
|
2271
|
+
// getAgentInfoByName tests
|
|
2272
|
+
// ===========================================================================
|
|
2273
|
+
describe("getAgentInfoByName", () => {
|
|
2274
|
+
it("returns info for existing agent", async () => {
|
|
2275
|
+
await createAgentConfig("info-by-name-agent", {
|
|
2276
|
+
name: "info-by-name-agent",
|
|
2277
|
+
description: "Test agent for getAgentInfoByName",
|
|
2278
|
+
});
|
|
2279
|
+
|
|
2280
|
+
const configPath = await createConfig({
|
|
2281
|
+
version: 1,
|
|
2282
|
+
agents: [{ path: "./agents/info-by-name-agent.yaml" }],
|
|
2283
|
+
});
|
|
2284
|
+
|
|
2285
|
+
const manager = new FleetManager({
|
|
2286
|
+
configPath,
|
|
2287
|
+
stateDir,
|
|
2288
|
+
logger: createSilentLogger(),
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
await manager.initialize();
|
|
2292
|
+
|
|
2293
|
+
const info = await manager.getAgentInfoByName("info-by-name-agent");
|
|
2294
|
+
expect(info.name).toBe("info-by-name-agent");
|
|
2295
|
+
expect(info.description).toBe("Test agent for getAgentInfoByName");
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
it("throws AgentNotFoundError for unknown agent", async () => {
|
|
2299
|
+
await createAgentConfig("known-agent-info", {
|
|
2300
|
+
name: "known-agent-info",
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2303
|
+
const configPath = await createConfig({
|
|
2304
|
+
version: 1,
|
|
2305
|
+
agents: [{ path: "./agents/known-agent-info.yaml" }],
|
|
2306
|
+
});
|
|
2307
|
+
|
|
2308
|
+
const manager = new FleetManager({
|
|
2309
|
+
configPath,
|
|
2310
|
+
stateDir,
|
|
2311
|
+
logger: createSilentLogger(),
|
|
2312
|
+
});
|
|
2313
|
+
|
|
2314
|
+
await manager.initialize();
|
|
2315
|
+
|
|
2316
|
+
await expect(
|
|
2317
|
+
manager.getAgentInfoByName("nonexistent-agent")
|
|
2318
|
+
).rejects.toThrow(AgentNotFoundError);
|
|
2319
|
+
});
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
// ===========================================================================
|
|
2323
|
+
// forkJob error cases
|
|
2324
|
+
// ===========================================================================
|
|
2325
|
+
describe("forkJob error cases", () => {
|
|
2326
|
+
it("throws JobForkError when job not found", async () => {
|
|
2327
|
+
await createAgentConfig("fork-error-agent", {
|
|
2328
|
+
name: "fork-error-agent",
|
|
2329
|
+
});
|
|
2330
|
+
|
|
2331
|
+
const configPath = await createConfig({
|
|
2332
|
+
version: 1,
|
|
2333
|
+
agents: [{ path: "./agents/fork-error-agent.yaml" }],
|
|
2334
|
+
});
|
|
2335
|
+
|
|
2336
|
+
const manager = new FleetManager({
|
|
2337
|
+
configPath,
|
|
2338
|
+
stateDir,
|
|
2339
|
+
logger: createSilentLogger(),
|
|
2340
|
+
});
|
|
2341
|
+
|
|
2342
|
+
await manager.initialize();
|
|
2343
|
+
|
|
2344
|
+
const { JobForkError } = await import("../errors.js");
|
|
2345
|
+
await expect(
|
|
2346
|
+
manager.forkJob("job-2099-01-01-nonexistent")
|
|
2347
|
+
).rejects.toThrow(JobForkError);
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
it("throws InvalidStateError when not initialized", async () => {
|
|
2351
|
+
const configPath = await createConfig({
|
|
2352
|
+
version: 1,
|
|
2353
|
+
agents: [],
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
const manager = new FleetManager({
|
|
2357
|
+
configPath,
|
|
2358
|
+
stateDir,
|
|
2359
|
+
logger: createSilentLogger(),
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2362
|
+
const { InvalidStateError } = await import("../errors.js");
|
|
2363
|
+
await expect(
|
|
2364
|
+
manager.forkJob("any-job-id")
|
|
2365
|
+
).rejects.toThrow(InvalidStateError);
|
|
2366
|
+
});
|
|
2367
|
+
});
|
|
2368
|
+
|
|
2369
|
+
// ===========================================================================
|
|
2370
|
+
// cancelJob error cases
|
|
2371
|
+
// ===========================================================================
|
|
2372
|
+
describe("cancelJob error cases", () => {
|
|
2373
|
+
it("throws JobNotFoundError when job not found", async () => {
|
|
2374
|
+
await createAgentConfig("cancel-error-agent", {
|
|
2375
|
+
name: "cancel-error-agent",
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
const configPath = await createConfig({
|
|
2379
|
+
version: 1,
|
|
2380
|
+
agents: [{ path: "./agents/cancel-error-agent.yaml" }],
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2383
|
+
const manager = new FleetManager({
|
|
2384
|
+
configPath,
|
|
2385
|
+
stateDir,
|
|
2386
|
+
logger: createSilentLogger(),
|
|
2387
|
+
});
|
|
2388
|
+
|
|
2389
|
+
await manager.initialize();
|
|
2390
|
+
|
|
2391
|
+
const { JobNotFoundError } = await import("../errors.js");
|
|
2392
|
+
await expect(
|
|
2393
|
+
manager.cancelJob("job-2099-01-01-nonexistent")
|
|
2394
|
+
).rejects.toThrow(JobNotFoundError);
|
|
2395
|
+
});
|
|
2396
|
+
|
|
2397
|
+
it("throws InvalidStateError when not initialized", async () => {
|
|
2398
|
+
const configPath = await createConfig({
|
|
2399
|
+
version: 1,
|
|
2400
|
+
agents: [],
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
const manager = new FleetManager({
|
|
2404
|
+
configPath,
|
|
2405
|
+
stateDir,
|
|
2406
|
+
logger: createSilentLogger(),
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
const { InvalidStateError } = await import("../errors.js");
|
|
2410
|
+
await expect(
|
|
2411
|
+
manager.cancelJob("any-job-id")
|
|
2412
|
+
).rejects.toThrow(InvalidStateError);
|
|
2413
|
+
});
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
// ===========================================================================
|
|
2417
|
+
// streamJobOutput tests
|
|
2418
|
+
// ===========================================================================
|
|
2419
|
+
describe("streamJobOutput error cases", () => {
|
|
2420
|
+
it("throws JobNotFoundError for non-existent job", async () => {
|
|
2421
|
+
await createAgentConfig("stream-error-agent", {
|
|
2422
|
+
name: "stream-error-agent",
|
|
2423
|
+
});
|
|
2424
|
+
|
|
2425
|
+
const configPath = await createConfig({
|
|
2426
|
+
version: 1,
|
|
2427
|
+
agents: [{ path: "./agents/stream-error-agent.yaml" }],
|
|
2428
|
+
});
|
|
2429
|
+
|
|
2430
|
+
const manager = new FleetManager({
|
|
2431
|
+
configPath,
|
|
2432
|
+
stateDir,
|
|
2433
|
+
logger: createSilentLogger(),
|
|
2434
|
+
});
|
|
2435
|
+
|
|
2436
|
+
await manager.initialize();
|
|
2437
|
+
|
|
2438
|
+
const stream = manager.streamJobOutput("job-2099-01-01-nonexistent");
|
|
2439
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
2440
|
+
|
|
2441
|
+
const { JobNotFoundError } = await import("../errors.js");
|
|
2442
|
+
await expect(iterator.next()).rejects.toThrow(JobNotFoundError);
|
|
2443
|
+
});
|
|
2444
|
+
});
|
|
2445
|
+
|
|
2446
|
+
// ===========================================================================
|
|
2447
|
+
// trigger edge cases
|
|
2448
|
+
// ===========================================================================
|
|
2449
|
+
describe("trigger edge cases", () => {
|
|
2450
|
+
it("triggers with schedule that has prompt", async () => {
|
|
2451
|
+
await createAgentConfig("trigger-schedule-prompt", {
|
|
2452
|
+
name: "trigger-schedule-prompt",
|
|
2453
|
+
schedules: {
|
|
2454
|
+
hourly: {
|
|
2455
|
+
type: "interval",
|
|
2456
|
+
interval: "1h",
|
|
2457
|
+
prompt: "Hourly check prompt",
|
|
2458
|
+
},
|
|
2459
|
+
},
|
|
2460
|
+
});
|
|
2461
|
+
|
|
2462
|
+
const configPath = await createConfig({
|
|
2463
|
+
version: 1,
|
|
2464
|
+
agents: [{ path: "./agents/trigger-schedule-prompt.yaml" }],
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
const manager = new FleetManager({
|
|
2468
|
+
configPath,
|
|
2469
|
+
stateDir,
|
|
2470
|
+
logger: createSilentLogger(),
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
await manager.initialize();
|
|
2474
|
+
|
|
2475
|
+
const result = await manager.trigger("trigger-schedule-prompt", "hourly");
|
|
2476
|
+
|
|
2477
|
+
expect(result.prompt).toBe("Hourly check prompt");
|
|
2478
|
+
expect(result.scheduleName).toBe("hourly");
|
|
2479
|
+
});
|
|
2480
|
+
|
|
2481
|
+
it("throws InvalidStateError when triggering before initialization", async () => {
|
|
2482
|
+
const configPath = await createConfig({
|
|
2483
|
+
version: 1,
|
|
2484
|
+
agents: [],
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
const manager = new FleetManager({
|
|
2488
|
+
configPath,
|
|
2489
|
+
stateDir,
|
|
2490
|
+
logger: createSilentLogger(),
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
const { InvalidStateError } = await import("../errors.js");
|
|
2494
|
+
await expect(manager.trigger("any-agent")).rejects.toThrow(InvalidStateError);
|
|
2495
|
+
});
|
|
2496
|
+
|
|
2497
|
+
it("throws AgentNotFoundError for unknown agent", async () => {
|
|
2498
|
+
await createAgentConfig("known-trigger-agent", {
|
|
2499
|
+
name: "known-trigger-agent",
|
|
2500
|
+
});
|
|
2501
|
+
|
|
2502
|
+
const configPath = await createConfig({
|
|
2503
|
+
version: 1,
|
|
2504
|
+
agents: [{ path: "./agents/known-trigger-agent.yaml" }],
|
|
2505
|
+
});
|
|
2506
|
+
|
|
2507
|
+
const manager = new FleetManager({
|
|
2508
|
+
configPath,
|
|
2509
|
+
stateDir,
|
|
2510
|
+
logger: createSilentLogger(),
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
await manager.initialize();
|
|
2514
|
+
|
|
2515
|
+
await expect(manager.trigger("unknown-agent")).rejects.toThrow(AgentNotFoundError);
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2518
|
+
it("throws ScheduleNotFoundError for unknown schedule", async () => {
|
|
2519
|
+
await createAgentConfig("known-schedule-trigger", {
|
|
2520
|
+
name: "known-schedule-trigger",
|
|
2521
|
+
schedules: {
|
|
2522
|
+
known: { type: "interval", interval: "1h" },
|
|
2523
|
+
},
|
|
2524
|
+
});
|
|
2525
|
+
|
|
2526
|
+
const configPath = await createConfig({
|
|
2527
|
+
version: 1,
|
|
2528
|
+
agents: [{ path: "./agents/known-schedule-trigger.yaml" }],
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2531
|
+
const manager = new FleetManager({
|
|
2532
|
+
configPath,
|
|
2533
|
+
stateDir,
|
|
2534
|
+
logger: createSilentLogger(),
|
|
2535
|
+
});
|
|
2536
|
+
|
|
2537
|
+
await manager.initialize();
|
|
2538
|
+
|
|
2539
|
+
await expect(
|
|
2540
|
+
manager.trigger("known-schedule-trigger", "unknown-schedule")
|
|
2541
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
2542
|
+
});
|
|
2543
|
+
});
|
|
2544
|
+
|
|
2545
|
+
// ===========================================================================
|
|
2546
|
+
// reload edge cases
|
|
2547
|
+
// ===========================================================================
|
|
2548
|
+
describe("reload edge cases", () => {
|
|
2549
|
+
it("throws InvalidStateError when not initialized", async () => {
|
|
2550
|
+
const configPath = await createConfig({
|
|
2551
|
+
version: 1,
|
|
2552
|
+
agents: [],
|
|
2553
|
+
});
|
|
2554
|
+
|
|
2555
|
+
const manager = new FleetManager({
|
|
2556
|
+
configPath,
|
|
2557
|
+
stateDir,
|
|
2558
|
+
logger: createSilentLogger(),
|
|
2559
|
+
});
|
|
2560
|
+
|
|
2561
|
+
const { InvalidStateError } = await import("../errors.js");
|
|
2562
|
+
await expect(manager.reload()).rejects.toThrow(InvalidStateError);
|
|
2563
|
+
});
|
|
2564
|
+
|
|
2565
|
+
it("handles removed schedule from existing agent", async () => {
|
|
2566
|
+
await createAgentConfig("schedule-remove-agent", {
|
|
2567
|
+
name: "schedule-remove-agent",
|
|
2568
|
+
schedules: {
|
|
2569
|
+
keep: { type: "interval", interval: "1h" },
|
|
2570
|
+
remove: { type: "interval", interval: "2h" },
|
|
2571
|
+
},
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
const configPath = await createConfig({
|
|
2575
|
+
version: 1,
|
|
2576
|
+
agents: [{ path: "./agents/schedule-remove-agent.yaml" }],
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
const manager = new FleetManager({
|
|
2580
|
+
configPath,
|
|
2581
|
+
stateDir,
|
|
2582
|
+
logger: createSilentLogger(),
|
|
2583
|
+
});
|
|
2584
|
+
|
|
2585
|
+
await manager.initialize();
|
|
2586
|
+
|
|
2587
|
+
// Remove one schedule
|
|
2588
|
+
await createAgentConfig("schedule-remove-agent", {
|
|
2589
|
+
name: "schedule-remove-agent",
|
|
2590
|
+
schedules: {
|
|
2591
|
+
keep: { type: "interval", interval: "1h" },
|
|
2592
|
+
},
|
|
2593
|
+
});
|
|
2594
|
+
|
|
2595
|
+
const result = await manager.reload();
|
|
2596
|
+
|
|
2597
|
+
expect(result.changes).toContainEqual(
|
|
2598
|
+
expect.objectContaining({
|
|
2599
|
+
type: "removed",
|
|
2600
|
+
category: "schedule",
|
|
2601
|
+
name: "schedule-remove-agent/remove",
|
|
2602
|
+
})
|
|
2603
|
+
);
|
|
2604
|
+
});
|
|
2605
|
+
|
|
2606
|
+
it("handles added schedule to existing agent", async () => {
|
|
2607
|
+
await createAgentConfig("schedule-add-agent", {
|
|
2608
|
+
name: "schedule-add-agent",
|
|
2609
|
+
schedules: {
|
|
2610
|
+
original: { type: "interval", interval: "1h" },
|
|
2611
|
+
},
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2614
|
+
const configPath = await createConfig({
|
|
2615
|
+
version: 1,
|
|
2616
|
+
agents: [{ path: "./agents/schedule-add-agent.yaml" }],
|
|
2617
|
+
});
|
|
2618
|
+
|
|
2619
|
+
const manager = new FleetManager({
|
|
2620
|
+
configPath,
|
|
2621
|
+
stateDir,
|
|
2622
|
+
logger: createSilentLogger(),
|
|
2623
|
+
});
|
|
2624
|
+
|
|
2625
|
+
await manager.initialize();
|
|
2626
|
+
|
|
2627
|
+
// Add a schedule
|
|
2628
|
+
await createAgentConfig("schedule-add-agent", {
|
|
2629
|
+
name: "schedule-add-agent",
|
|
2630
|
+
schedules: {
|
|
2631
|
+
original: { type: "interval", interval: "1h" },
|
|
2632
|
+
newschedule: { type: "interval", interval: "2h" },
|
|
2633
|
+
},
|
|
2634
|
+
});
|
|
2635
|
+
|
|
2636
|
+
const result = await manager.reload();
|
|
2637
|
+
|
|
2638
|
+
expect(result.changes).toContainEqual(
|
|
2639
|
+
expect.objectContaining({
|
|
2640
|
+
type: "added",
|
|
2641
|
+
category: "schedule",
|
|
2642
|
+
name: "schedule-add-agent/newschedule",
|
|
2643
|
+
})
|
|
2644
|
+
);
|
|
2645
|
+
});
|
|
2646
|
+
});
|
|
2647
|
+
|
|
2648
|
+
// ===========================================================================
|
|
2649
|
+
// start error cases
|
|
2650
|
+
// ===========================================================================
|
|
2651
|
+
describe("start error cases", () => {
|
|
2652
|
+
it("throws InvalidStateError when not initialized", async () => {
|
|
2653
|
+
const configPath = await createConfig({
|
|
2654
|
+
version: 1,
|
|
2655
|
+
agents: [],
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2658
|
+
const manager = new FleetManager({
|
|
2659
|
+
configPath,
|
|
2660
|
+
stateDir,
|
|
2661
|
+
logger: createSilentLogger(),
|
|
2662
|
+
});
|
|
2663
|
+
|
|
2664
|
+
const { FleetManagerStateError } = await import("../errors.js");
|
|
2665
|
+
await expect(manager.start()).rejects.toThrow(FleetManagerStateError);
|
|
2666
|
+
});
|
|
2667
|
+
});
|
|
2668
|
+
|
|
2669
|
+
// ===========================================================================
|
|
2670
|
+
// getSchedule tests
|
|
2671
|
+
// ===========================================================================
|
|
2672
|
+
describe("getSchedule", () => {
|
|
2673
|
+
it("returns schedule info for valid agent and schedule", async () => {
|
|
2674
|
+
await createAgentConfig("get-schedule-agent", {
|
|
2675
|
+
name: "get-schedule-agent",
|
|
2676
|
+
schedules: {
|
|
2677
|
+
hourly: { type: "interval", interval: "1h" },
|
|
2678
|
+
},
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2681
|
+
const configPath = await createConfig({
|
|
2682
|
+
version: 1,
|
|
2683
|
+
agents: [{ path: "./agents/get-schedule-agent.yaml" }],
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
const manager = new FleetManager({
|
|
2687
|
+
configPath,
|
|
2688
|
+
stateDir,
|
|
2689
|
+
logger: createSilentLogger(),
|
|
2690
|
+
});
|
|
2691
|
+
|
|
2692
|
+
await manager.initialize();
|
|
2693
|
+
|
|
2694
|
+
const schedule = await manager.getSchedule("get-schedule-agent", "hourly");
|
|
2695
|
+
expect(schedule.name).toBe("hourly");
|
|
2696
|
+
expect(schedule.agentName).toBe("get-schedule-agent");
|
|
2697
|
+
expect(schedule.type).toBe("interval");
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
it("throws AgentNotFoundError for unknown agent", async () => {
|
|
2701
|
+
await createAgentConfig("known-get-schedule", {
|
|
2702
|
+
name: "known-get-schedule",
|
|
2703
|
+
schedules: {
|
|
2704
|
+
test: { type: "interval", interval: "1h" },
|
|
2705
|
+
},
|
|
2706
|
+
});
|
|
2707
|
+
|
|
2708
|
+
const configPath = await createConfig({
|
|
2709
|
+
version: 1,
|
|
2710
|
+
agents: [{ path: "./agents/known-get-schedule.yaml" }],
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
const manager = new FleetManager({
|
|
2714
|
+
configPath,
|
|
2715
|
+
stateDir,
|
|
2716
|
+
logger: createSilentLogger(),
|
|
2717
|
+
});
|
|
2718
|
+
|
|
2719
|
+
await manager.initialize();
|
|
2720
|
+
|
|
2721
|
+
await expect(
|
|
2722
|
+
manager.getSchedule("unknown-agent", "test")
|
|
2723
|
+
).rejects.toThrow(AgentNotFoundError);
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
it("throws ScheduleNotFoundError for unknown schedule", async () => {
|
|
2727
|
+
await createAgentConfig("known-agent-get-schedule", {
|
|
2728
|
+
name: "known-agent-get-schedule",
|
|
2729
|
+
schedules: {
|
|
2730
|
+
known: { type: "interval", interval: "1h" },
|
|
2731
|
+
},
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
const configPath = await createConfig({
|
|
2735
|
+
version: 1,
|
|
2736
|
+
agents: [{ path: "./agents/known-agent-get-schedule.yaml" }],
|
|
2737
|
+
});
|
|
2738
|
+
|
|
2739
|
+
const manager = new FleetManager({
|
|
2740
|
+
configPath,
|
|
2741
|
+
stateDir,
|
|
2742
|
+
logger: createSilentLogger(),
|
|
2743
|
+
});
|
|
2744
|
+
|
|
2745
|
+
await manager.initialize();
|
|
2746
|
+
|
|
2747
|
+
await expect(
|
|
2748
|
+
manager.getSchedule("known-agent-get-schedule", "unknown")
|
|
2749
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
2750
|
+
});
|
|
2751
|
+
|
|
2752
|
+
it("throws ScheduleNotFoundError when agent has no schedules", async () => {
|
|
2753
|
+
await createAgentConfig("no-schedules-agent", {
|
|
2754
|
+
name: "no-schedules-agent",
|
|
2755
|
+
});
|
|
2756
|
+
|
|
2757
|
+
const configPath = await createConfig({
|
|
2758
|
+
version: 1,
|
|
2759
|
+
agents: [{ path: "./agents/no-schedules-agent.yaml" }],
|
|
2760
|
+
});
|
|
2761
|
+
|
|
2762
|
+
const manager = new FleetManager({
|
|
2763
|
+
configPath,
|
|
2764
|
+
stateDir,
|
|
2765
|
+
logger: createSilentLogger(),
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
await manager.initialize();
|
|
2769
|
+
|
|
2770
|
+
await expect(
|
|
2771
|
+
manager.getSchedule("no-schedules-agent", "any")
|
|
2772
|
+
).rejects.toThrow(ScheduleNotFoundError);
|
|
2773
|
+
});
|
|
2774
|
+
});
|
|
2775
|
+
|
|
2776
|
+
// ===========================================================================
|
|
2777
|
+
// streamJobOutput for completed job
|
|
2778
|
+
// ===========================================================================
|
|
2779
|
+
describe("streamJobOutput for completed jobs", () => {
|
|
2780
|
+
it("streams output for completed job and stops", async () => {
|
|
2781
|
+
await createAgentConfig("completed-stream-agent", {
|
|
2782
|
+
name: "completed-stream-agent",
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
const configPath = await createConfig({
|
|
2786
|
+
version: 1,
|
|
2787
|
+
agents: [{ path: "./agents/completed-stream-agent.yaml" }],
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
const manager = new FleetManager({
|
|
2791
|
+
configPath,
|
|
2792
|
+
stateDir,
|
|
2793
|
+
logger: createSilentLogger(),
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
await manager.initialize();
|
|
2797
|
+
|
|
2798
|
+
// Create a job and cancel it immediately to make it "completed"
|
|
2799
|
+
const result = await manager.trigger("completed-stream-agent");
|
|
2800
|
+
await manager.cancelJob(result.jobId);
|
|
2801
|
+
|
|
2802
|
+
// Stream should complete quickly for cancelled job
|
|
2803
|
+
const stream = manager.streamJobOutput(result.jobId);
|
|
2804
|
+
const entries: unknown[] = [];
|
|
2805
|
+
|
|
2806
|
+
// Use a short timeout for the test
|
|
2807
|
+
const timeout = setTimeout(() => {}, 100);
|
|
2808
|
+
|
|
2809
|
+
try {
|
|
2810
|
+
for await (const entry of stream) {
|
|
2811
|
+
entries.push(entry);
|
|
2812
|
+
// Break after first entry or timeout
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
} finally {
|
|
2816
|
+
clearTimeout(timeout);
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// The stream should have yielded at least some entries or completed
|
|
2820
|
+
expect(stream[Symbol.asyncIterator]).toBeDefined();
|
|
2821
|
+
});
|
|
2822
|
+
});
|
|
2823
|
+
|
|
2824
|
+
// ===========================================================================
|
|
2825
|
+
// initialize edge cases
|
|
2826
|
+
// ===========================================================================
|
|
2827
|
+
describe("initialize edge cases", () => {
|
|
2828
|
+
it("emits started event", async () => {
|
|
2829
|
+
await createAgentConfig("emit-started-agent-1", {
|
|
2830
|
+
name: "emit-started-agent-1",
|
|
2831
|
+
});
|
|
2832
|
+
await createAgentConfig("emit-started-agent-2", {
|
|
2833
|
+
name: "emit-started-agent-2",
|
|
2834
|
+
});
|
|
2835
|
+
|
|
2836
|
+
const configPath = await createConfig({
|
|
2837
|
+
version: 1,
|
|
2838
|
+
agents: [
|
|
2839
|
+
{ path: "./agents/emit-started-agent-1.yaml" },
|
|
2840
|
+
{ path: "./agents/emit-started-agent-2.yaml" },
|
|
2841
|
+
],
|
|
2842
|
+
});
|
|
2843
|
+
|
|
2844
|
+
const manager = new FleetManager({
|
|
2845
|
+
configPath,
|
|
2846
|
+
stateDir,
|
|
2847
|
+
logger: createSilentLogger(),
|
|
2848
|
+
});
|
|
2849
|
+
|
|
2850
|
+
const startedHandler = vi.fn();
|
|
2851
|
+
manager.on("started", startedHandler);
|
|
2852
|
+
|
|
2853
|
+
await manager.initialize();
|
|
2854
|
+
await manager.start();
|
|
2855
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2856
|
+
|
|
2857
|
+
await manager.stop();
|
|
2858
|
+
|
|
2859
|
+
// started event is emitted when fleet starts
|
|
2860
|
+
expect(startedHandler).toHaveBeenCalled();
|
|
2861
|
+
});
|
|
2862
|
+
});
|
|
2863
|
+
});
|
|
2864
|
+
|
|
2865
|
+
|
|
2866
|
+
|
|
2867
|
+
|
|
2868
|
+
|
|
2869
|
+
|