crewly 1.8.9 → 1.8.11
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/config/constants.d.ts.map +1 -0
- package/config/index.d.ts.map +1 -0
- package/config/roles/_common/memory-instructions.md +6 -5
- package/config/roles/_common/wiki-instructions.md +49 -0
- package/config/roles/architect/prompt.md +2 -2
- package/config/roles/backend-developer/prompt.md +2 -2
- package/config/roles/designer/prompt.md +2 -2
- package/config/roles/developer/prompt.md +2 -2
- package/config/roles/frontend-developer/prompt.md +2 -2
- package/config/roles/fullstack-dev/prompt.md +2 -2
- package/config/roles/generalist/prompt.md +2 -2
- package/config/roles/ops/prompt.md +2 -2
- package/config/roles/orchestrator/prompt.md +135 -11
- package/config/roles/product-manager/prompt.md +2 -2
- package/config/roles/qa/prompt.md +2 -2
- package/config/roles/qa-engineer/prompt.md +2 -2
- package/config/roles/researcher/prompt.md +15 -6
- package/config/roles/sales/prompt.md +2 -2
- package/config/roles/support/prompt.md +2 -2
- package/config/roles/team-leader/prompt.md +17 -2
- package/config/roles/tpm/prompt.md +2 -2
- package/config/roles/ux-designer/prompt.md +2 -2
- package/config/skills/orchestrator/wiki-cleanup/SKILL.md +89 -0
- package/config/skills/orchestrator/wiki-cleanup/execute.sh +139 -0
- package/config/skills/orchestrator/wiki-lint/SKILL.md +75 -0
- package/config/skills/orchestrator/wiki-lint/execute.sh +66 -0
- package/config/skills/orchestrator/wiki-migrate/SKILL.md +103 -0
- package/config/skills/orchestrator/wiki-migrate/execute.sh +82 -0
- package/config/skills/orchestrator/wiki-process-queue/SKILL.md +9 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts +169 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +1779 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +18 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +63 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.routes.js +5 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +109 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +418 -4
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +11 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +64 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/index.js.orc-bak-20260529 +3130 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts +513 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js +1568 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.d.ts +86 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.js +147 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.d.ts +68 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.js +131 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.d.ts +130 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.js +263 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.d.ts +74 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.js +140 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.d.ts +29 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.js +279 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.d.ts +340 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.js +1176 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.d.ts +79 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.js +145 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.d.ts +79 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.js +218 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.d.ts +16 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.js +16 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.d.ts +135 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.js +185 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +141 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +310 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.d.ts +91 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.js +143 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.d.ts +103 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.js +256 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.d.ts +143 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.js +264 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.d.ts +13 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.js +91 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.d.ts +135 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.js +1937 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.js.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +1 -1
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts +429 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js +852 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts +171 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js +725 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +50 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +45 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js +34 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.d.ts +118 -0
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.js +326 -0
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.d.ts +72 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.js +186 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +4 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +24 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.d.ts +160 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js +399 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.d.ts +182 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.js +505 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts +232 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js +1416 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.d.ts +51 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.js +102 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.js +156 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts +90 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js +190 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts +164 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +675 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -0
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +65 -0
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/auto-assign.types.d.ts +271 -0
- package/dist/backend/backend/src/types/auto-assign.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/auto-assign.types.js +136 -0
- package/dist/backend/backend/src/types/auto-assign.types.js.map +1 -0
- package/dist/backend/backend/src/types/cron-task.types.d.ts +16 -1
- package/dist/backend/backend/src/types/cron-task.types.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/esm-require.utils.d.ts +111 -0
- package/dist/backend/backend/src/utils/esm-require.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/esm-require.utils.js +124 -0
- package/dist/backend/backend/src/utils/esm-require.utils.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts +220 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.js +37 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.js.map +1 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.d.ts +56 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.d.ts.map +1 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.js +91 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.js.map +1 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.d.ts +159 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.js +304 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.js.map +1 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.d.ts +115 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.js +215 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.js.map +1 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.d.ts +78 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.js +179 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.js.map +1 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.d.ts +331 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.js +814 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.js.map +1 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.d.ts +171 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.js +725 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.js.map +1 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +45 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/types/auto-assign.types.d.ts +271 -0
- package/dist/cli/backend/src/types/auto-assign.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/auto-assign.types.js +136 -0
- package/dist/cli/backend/src/types/auto-assign.types.js.map +1 -0
- package/dist/cli/cli/src/index.js +0 -0
- package/frontend/dist/assets/{index-db3f5041.css → index-068bb4f6.css} +10 -1
- package/frontend/dist/assets/index-c24ceb15.js +4960 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/config/skills/agent/core/query-knowledge/SKILL.md +0 -87
- package/config/skills/agent/core/query-knowledge/execute.sh +0 -30
- package/config/skills/orchestrator/query-knowledge/SKILL.md +0 -75
- package/config/skills/orchestrator/query-knowledge/execute.sh +0 -30
- package/frontend/dist/assets/index-cc115bb4.js +0 -4926
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Assignment Service
|
|
3
|
+
*
|
|
4
|
+
* Automatically assigns tasks to idle agents based on role matching,
|
|
5
|
+
* priority, and dependency rules.
|
|
6
|
+
*
|
|
7
|
+
* @module services/autonomous/auto-assign.service
|
|
8
|
+
*/
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as fs from 'fs/promises';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { EventEmitter } from 'events';
|
|
13
|
+
import { parse as parseYAML } from 'yaml';
|
|
14
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
15
|
+
import { TaskService } from '../project/task.service.js';
|
|
16
|
+
import { TaskTrackingService } from '../project/task-tracking.service.js';
|
|
17
|
+
import { AUTO_ASSIGN_CONSTANTS, DEFAULT_AUTO_ASSIGN_CONFIG, DEFAULT_ASSIGNMENT_STRATEGY, } from '../../types/auto-assign.types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Service for automatic task assignment to agents
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const service = AutoAssignService.getInstance();
|
|
24
|
+
* await service.initialize('/path/to/project');
|
|
25
|
+
*
|
|
26
|
+
* // Assign next task to an idle agent
|
|
27
|
+
* const result = await service.assignNextTask('agent-session');
|
|
28
|
+
* if (result) {
|
|
29
|
+
* console.log(`Assigned task ${result.taskId} to agent`);
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class AutoAssignService extends EventEmitter {
|
|
34
|
+
static instance = null;
|
|
35
|
+
logger;
|
|
36
|
+
taskService = null;
|
|
37
|
+
taskTrackingService = null;
|
|
38
|
+
/** Configuration cache per project */
|
|
39
|
+
configs = new Map();
|
|
40
|
+
/** Task queue cache per project */
|
|
41
|
+
queues = new Map();
|
|
42
|
+
/** Paused projects */
|
|
43
|
+
paused = new Set();
|
|
44
|
+
/** Agent to project mapping */
|
|
45
|
+
agentProjects = new Map();
|
|
46
|
+
/** Assignment history per project */
|
|
47
|
+
assignmentHistory = new Map();
|
|
48
|
+
/** Daily assignment counts per agent */
|
|
49
|
+
dailyAssignments = new Map();
|
|
50
|
+
/** Last assignment time per agent */
|
|
51
|
+
lastAssignmentTime = new Map();
|
|
52
|
+
/**
|
|
53
|
+
* Private constructor for singleton pattern
|
|
54
|
+
*/
|
|
55
|
+
constructor() {
|
|
56
|
+
super();
|
|
57
|
+
this.logger = LoggerService.getInstance().createComponentLogger('AutoAssignService');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets the singleton instance
|
|
61
|
+
*
|
|
62
|
+
* @returns The AutoAssignService instance
|
|
63
|
+
*/
|
|
64
|
+
static getInstance() {
|
|
65
|
+
if (!AutoAssignService.instance) {
|
|
66
|
+
AutoAssignService.instance = new AutoAssignService();
|
|
67
|
+
}
|
|
68
|
+
return AutoAssignService.instance;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clears the singleton instance (for testing)
|
|
72
|
+
*/
|
|
73
|
+
static clearInstance() {
|
|
74
|
+
AutoAssignService.instance = null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Set the task service (for dependency injection/testing)
|
|
78
|
+
*
|
|
79
|
+
* @param service - Task service instance
|
|
80
|
+
*/
|
|
81
|
+
setTaskService(service) {
|
|
82
|
+
this.taskService = service;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Set the task tracking service (for dependency injection/testing)
|
|
86
|
+
*
|
|
87
|
+
* @param service - Task tracking service instance
|
|
88
|
+
*/
|
|
89
|
+
setTaskTrackingService(service) {
|
|
90
|
+
this.taskTrackingService = service;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get the task service, creating one if needed
|
|
94
|
+
*
|
|
95
|
+
* @param projectPath - Project path
|
|
96
|
+
* @returns Task service
|
|
97
|
+
*/
|
|
98
|
+
getTaskService(projectPath) {
|
|
99
|
+
if (!this.taskService) {
|
|
100
|
+
this.taskService = new TaskService(projectPath);
|
|
101
|
+
}
|
|
102
|
+
return this.taskService;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the task tracking service, creating one if needed
|
|
106
|
+
*
|
|
107
|
+
* @returns Task tracking service
|
|
108
|
+
*/
|
|
109
|
+
getTaskTrackingService() {
|
|
110
|
+
if (!this.taskTrackingService) {
|
|
111
|
+
this.taskTrackingService = new TaskTrackingService();
|
|
112
|
+
}
|
|
113
|
+
return this.taskTrackingService;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Initialize the service for a project
|
|
117
|
+
*
|
|
118
|
+
* @param projectPath - Path to the project
|
|
119
|
+
*/
|
|
120
|
+
async initialize(projectPath) {
|
|
121
|
+
// Load or create config
|
|
122
|
+
const config = await this.loadConfig(projectPath);
|
|
123
|
+
this.configs.set(projectPath, config);
|
|
124
|
+
// Build initial queue
|
|
125
|
+
await this.refreshQueue(projectPath);
|
|
126
|
+
// Subscribe to task events
|
|
127
|
+
this.subscribeToEvents(projectPath);
|
|
128
|
+
this.logger.info('AutoAssignService initialized', { projectPath });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Load configuration from file or return defaults
|
|
132
|
+
*
|
|
133
|
+
* @param projectPath - Project path
|
|
134
|
+
* @returns Auto-assign configuration
|
|
135
|
+
*/
|
|
136
|
+
async loadConfig(projectPath) {
|
|
137
|
+
const configPath = path.join(projectPath, AUTO_ASSIGN_CONSTANTS.PATHS.CONFIG_DIR, AUTO_ASSIGN_CONSTANTS.PATHS.CONFIG_FILE);
|
|
138
|
+
try {
|
|
139
|
+
if (existsSync(configPath)) {
|
|
140
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
141
|
+
const parsed = parseYAML(content);
|
|
142
|
+
// Merge with defaults
|
|
143
|
+
return {
|
|
144
|
+
enabled: parsed.enabled ?? DEFAULT_AUTO_ASSIGN_CONFIG.enabled,
|
|
145
|
+
strategy: {
|
|
146
|
+
...DEFAULT_ASSIGNMENT_STRATEGY,
|
|
147
|
+
...parsed.strategy,
|
|
148
|
+
roleMatching: parsed.strategy?.roleMatching ?? DEFAULT_ASSIGNMENT_STRATEGY.roleMatching,
|
|
149
|
+
loadBalancing: {
|
|
150
|
+
...DEFAULT_ASSIGNMENT_STRATEGY.loadBalancing,
|
|
151
|
+
...parsed.strategy?.loadBalancing,
|
|
152
|
+
},
|
|
153
|
+
dependencies: {
|
|
154
|
+
...DEFAULT_ASSIGNMENT_STRATEGY.dependencies,
|
|
155
|
+
...parsed.strategy?.dependencies,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
notifications: {
|
|
159
|
+
...DEFAULT_AUTO_ASSIGN_CONFIG.notifications,
|
|
160
|
+
...parsed.notifications,
|
|
161
|
+
},
|
|
162
|
+
limits: {
|
|
163
|
+
...DEFAULT_AUTO_ASSIGN_CONFIG.limits,
|
|
164
|
+
...parsed.limits,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
this.logger.warn('Failed to load config, using defaults', { projectPath, error });
|
|
171
|
+
}
|
|
172
|
+
return { ...DEFAULT_AUTO_ASSIGN_CONFIG };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the assignment strategy configuration for a project
|
|
176
|
+
*
|
|
177
|
+
* @param projectPath - Path to the project
|
|
178
|
+
* @returns Assignment strategy configuration
|
|
179
|
+
*/
|
|
180
|
+
async getConfig(projectPath) {
|
|
181
|
+
if (!this.configs.has(projectPath)) {
|
|
182
|
+
const config = await this.loadConfig(projectPath);
|
|
183
|
+
this.configs.set(projectPath, config);
|
|
184
|
+
}
|
|
185
|
+
return this.configs.get(projectPath).strategy;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Update the assignment strategy configuration
|
|
189
|
+
*
|
|
190
|
+
* @param projectPath - Path to the project
|
|
191
|
+
* @param config - Partial configuration to merge
|
|
192
|
+
*/
|
|
193
|
+
async setConfig(projectPath, config) {
|
|
194
|
+
const existing = this.configs.get(projectPath) || { ...DEFAULT_AUTO_ASSIGN_CONFIG };
|
|
195
|
+
existing.strategy = {
|
|
196
|
+
...existing.strategy,
|
|
197
|
+
...config,
|
|
198
|
+
};
|
|
199
|
+
this.configs.set(projectPath, existing);
|
|
200
|
+
this.logger.info('Config updated', { projectPath });
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Assign the next available task to an agent
|
|
204
|
+
*
|
|
205
|
+
* @param sessionName - Agent session name
|
|
206
|
+
* @returns Assignment result or null if no task available
|
|
207
|
+
*/
|
|
208
|
+
async assignNextTask(sessionName) {
|
|
209
|
+
const projectPath = this.agentProjects.get(sessionName);
|
|
210
|
+
if (!projectPath) {
|
|
211
|
+
this.logger.warn('No project associated with agent', { sessionName });
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
// Check if auto-assign is enabled and not paused
|
|
215
|
+
if (!(await this.isAutoAssignEnabled(projectPath))) {
|
|
216
|
+
this.logger.debug('Auto-assign disabled or paused', { projectPath });
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
// Check agent workload
|
|
220
|
+
const workload = await this.getAgentWorkload(sessionName);
|
|
221
|
+
const config = await this.getConfig(projectPath);
|
|
222
|
+
if (workload.currentTasks.length >= config.loadBalancing.maxConcurrentTasks) {
|
|
223
|
+
this.logger.debug('Agent at max capacity', { sessionName, currentTasks: workload.currentTasks.length });
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
// Check cooldown
|
|
227
|
+
const lastTime = this.lastAssignmentTime.get(sessionName) || 0;
|
|
228
|
+
const fullConfig = this.configs.get(projectPath) || DEFAULT_AUTO_ASSIGN_CONFIG;
|
|
229
|
+
const cooldownMs = fullConfig.limits.cooldownBetweenTasks * 1000;
|
|
230
|
+
if (Date.now() - lastTime < cooldownMs) {
|
|
231
|
+
this.logger.debug('Agent in cooldown period', { sessionName });
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
// Check daily limit
|
|
235
|
+
const dailyRecord = this.dailyAssignments.get(sessionName);
|
|
236
|
+
const today = new Date().toISOString().split('T')[0];
|
|
237
|
+
if (dailyRecord && dailyRecord.date === today) {
|
|
238
|
+
if (dailyRecord.count >= fullConfig.limits.maxAssignmentsPerDay) {
|
|
239
|
+
this.logger.debug('Agent at daily limit', { sessionName, count: dailyRecord.count });
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Find next task
|
|
244
|
+
const result = await this.findNextTask({
|
|
245
|
+
sessionName,
|
|
246
|
+
role: workload.role,
|
|
247
|
+
projectPath,
|
|
248
|
+
});
|
|
249
|
+
if (!result.found || !result.task) {
|
|
250
|
+
this.emitEvent({
|
|
251
|
+
type: 'no_tasks',
|
|
252
|
+
agentId: workload.agentId,
|
|
253
|
+
sessionName,
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
metadata: { reason: result.reason },
|
|
256
|
+
});
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
// Assign the task
|
|
260
|
+
return this.assignTask(result.task.taskPath, sessionName);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Find the next task suitable for a role
|
|
264
|
+
*
|
|
265
|
+
* @param params - Search parameters
|
|
266
|
+
* @returns Search result with task or reason for no match
|
|
267
|
+
*/
|
|
268
|
+
async findNextTask(params) {
|
|
269
|
+
const { role, projectPath, preferredTaskTypes } = params;
|
|
270
|
+
const queue = await this.getTaskQueue(projectPath);
|
|
271
|
+
const config = await this.getConfig(projectPath);
|
|
272
|
+
// Filter tasks by eligibility
|
|
273
|
+
const eligibleTasks = queue.tasks.filter((task) => {
|
|
274
|
+
// Check role match
|
|
275
|
+
if (task.requiredRole && task.requiredRole !== role) {
|
|
276
|
+
if (!this.canRoleHandleTask(role, task, config)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Check if task type matches role capabilities
|
|
281
|
+
if (task.taskType && !this.canRoleHandleTaskType(role, task.taskType, config)) {
|
|
282
|
+
// Check if it's an exclusive task type for another role
|
|
283
|
+
if (this.isExclusiveForOtherRole(role, task.taskType, config)) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Check dependencies
|
|
288
|
+
if (config.dependencies.respectBlocking && task.blockedBy.length > 0) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
});
|
|
293
|
+
if (eligibleTasks.length === 0) {
|
|
294
|
+
// Determine reason
|
|
295
|
+
if (queue.tasks.length === 0) {
|
|
296
|
+
return { found: false, reason: 'no_tasks' };
|
|
297
|
+
}
|
|
298
|
+
const blockedCount = queue.tasks.filter((t) => t.blockedBy.length > 0).length;
|
|
299
|
+
if (blockedCount === queue.tasks.length) {
|
|
300
|
+
return { found: false, reason: 'all_blocked' };
|
|
301
|
+
}
|
|
302
|
+
return { found: false, reason: 'role_mismatch' };
|
|
303
|
+
}
|
|
304
|
+
// Sort by priority and preference
|
|
305
|
+
const sorted = this.sortTasks(eligibleTasks, config, preferredTaskTypes);
|
|
306
|
+
return {
|
|
307
|
+
found: true,
|
|
308
|
+
task: sorted[0],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Sort tasks by priority and preferences
|
|
313
|
+
*
|
|
314
|
+
* @param tasks - Tasks to sort
|
|
315
|
+
* @param config - Assignment strategy
|
|
316
|
+
* @param preferredTypes - Preferred task types
|
|
317
|
+
* @returns Sorted tasks
|
|
318
|
+
*/
|
|
319
|
+
sortTasks(tasks, config, preferredTypes) {
|
|
320
|
+
return [...tasks].sort((a, b) => {
|
|
321
|
+
// Preferred types first
|
|
322
|
+
if (preferredTypes && preferredTypes.length > 0) {
|
|
323
|
+
const aPreferred = a.taskType && preferredTypes.includes(a.taskType);
|
|
324
|
+
const bPreferred = b.taskType && preferredTypes.includes(b.taskType);
|
|
325
|
+
if (aPreferred && !bPreferred)
|
|
326
|
+
return -1;
|
|
327
|
+
if (!aPreferred && bPreferred)
|
|
328
|
+
return 1;
|
|
329
|
+
}
|
|
330
|
+
// Then by prioritization strategy
|
|
331
|
+
if (config.prioritization === 'priority') {
|
|
332
|
+
// Lower priority number = more important (CRITICAL=1 is highest priority)
|
|
333
|
+
return a.priority - b.priority;
|
|
334
|
+
}
|
|
335
|
+
else if (config.prioritization === 'fifo') {
|
|
336
|
+
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
337
|
+
}
|
|
338
|
+
else if (config.prioritization === 'deadline') {
|
|
339
|
+
// Tasks with estimated hours should be done sooner
|
|
340
|
+
const aHours = a.estimatedHours ?? Infinity;
|
|
341
|
+
const bHours = b.estimatedHours ?? Infinity;
|
|
342
|
+
return aHours - bHours;
|
|
343
|
+
}
|
|
344
|
+
return 0;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check if a role can handle a specific task
|
|
349
|
+
*
|
|
350
|
+
* @param role - Agent role
|
|
351
|
+
* @param task - Task to check
|
|
352
|
+
* @param config - Assignment strategy
|
|
353
|
+
* @returns True if role can handle the task
|
|
354
|
+
*/
|
|
355
|
+
canRoleHandleTask(role, task, config) {
|
|
356
|
+
// If task requires a specific role and it doesn't match, check if the role can substitute
|
|
357
|
+
if (task.requiredRole && task.requiredRole !== role) {
|
|
358
|
+
// Check if the current role can substitute for the required role
|
|
359
|
+
// For example, 'developer' can do generic dev work, but not 'qa' tasks
|
|
360
|
+
const roleHierarchy = {
|
|
361
|
+
'frontend-developer': ['developer'],
|
|
362
|
+
'backend-developer': ['developer'],
|
|
363
|
+
developer: [],
|
|
364
|
+
pm: [],
|
|
365
|
+
tpm: [],
|
|
366
|
+
pgm: [],
|
|
367
|
+
qa: ['tester'],
|
|
368
|
+
tester: [],
|
|
369
|
+
orchestrator: [],
|
|
370
|
+
designer: [],
|
|
371
|
+
devops: [],
|
|
372
|
+
};
|
|
373
|
+
// Check if the agent's role is in the hierarchy chain for required role
|
|
374
|
+
const canSubstitute = roleHierarchy[role]?.includes(task.requiredRole) || false;
|
|
375
|
+
if (!canSubstitute) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Check task type compatibility if present
|
|
380
|
+
const rule = config.roleMatching.find((r) => r.role === role);
|
|
381
|
+
if (!rule)
|
|
382
|
+
return false;
|
|
383
|
+
if (task.taskType && !rule.taskTypes.includes(task.taskType)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Check if a role can handle a task type
|
|
390
|
+
*
|
|
391
|
+
* @param role - Agent role
|
|
392
|
+
* @param taskType - Task type
|
|
393
|
+
* @param config - Assignment strategy
|
|
394
|
+
* @returns True if role can handle the task type
|
|
395
|
+
*/
|
|
396
|
+
canRoleHandleTaskType(role, taskType, config) {
|
|
397
|
+
const rule = config.roleMatching.find((r) => r.role === role);
|
|
398
|
+
if (!rule)
|
|
399
|
+
return false;
|
|
400
|
+
return rule.taskTypes.includes(taskType);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Check if a task type is exclusive to another role
|
|
404
|
+
*
|
|
405
|
+
* @param role - Current role
|
|
406
|
+
* @param taskType - Task type
|
|
407
|
+
* @param config - Assignment strategy
|
|
408
|
+
* @returns True if exclusive to another role
|
|
409
|
+
*/
|
|
410
|
+
isExclusiveForOtherRole(role, taskType, config) {
|
|
411
|
+
for (const rule of config.roleMatching) {
|
|
412
|
+
if (rule.role !== role && rule.exclusive && rule.taskTypes.includes(taskType)) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Assign a specific task to an agent
|
|
420
|
+
*
|
|
421
|
+
* @param taskPath - Path to the task file
|
|
422
|
+
* @param sessionName - Agent session name
|
|
423
|
+
* @returns Assignment result
|
|
424
|
+
*/
|
|
425
|
+
async assignTask(taskPath, sessionName) {
|
|
426
|
+
const workload = await this.getAgentWorkload(sessionName);
|
|
427
|
+
const projectPath = this.agentProjects.get(sessionName);
|
|
428
|
+
if (!projectPath) {
|
|
429
|
+
throw new Error(`No project associated with session: ${sessionName}`);
|
|
430
|
+
}
|
|
431
|
+
// Get task info
|
|
432
|
+
const taskService = this.getTaskService(projectPath);
|
|
433
|
+
const tasks = await taskService.getAllTasks();
|
|
434
|
+
const task = tasks.find((t) => t.filePath === taskPath);
|
|
435
|
+
if (!task) {
|
|
436
|
+
throw new Error(`Task not found: ${taskPath}`);
|
|
437
|
+
}
|
|
438
|
+
const now = new Date().toISOString();
|
|
439
|
+
// Create assignment record
|
|
440
|
+
const assignment = {
|
|
441
|
+
taskId: task.id,
|
|
442
|
+
agentId: workload.agentId,
|
|
443
|
+
sessionName,
|
|
444
|
+
assignedAt: now,
|
|
445
|
+
status: 'active',
|
|
446
|
+
};
|
|
447
|
+
// Add to assignment history
|
|
448
|
+
const history = this.assignmentHistory.get(projectPath) || [];
|
|
449
|
+
history.push(assignment);
|
|
450
|
+
this.assignmentHistory.set(projectPath, history);
|
|
451
|
+
// Update daily count
|
|
452
|
+
const today = now.split('T')[0];
|
|
453
|
+
const dailyRecord = this.dailyAssignments.get(sessionName);
|
|
454
|
+
if (dailyRecord && dailyRecord.date === today) {
|
|
455
|
+
dailyRecord.count++;
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
this.dailyAssignments.set(sessionName, { date: today, count: 1 });
|
|
459
|
+
}
|
|
460
|
+
// Update last assignment time
|
|
461
|
+
this.lastAssignmentTime.set(sessionName, Date.now());
|
|
462
|
+
const result = {
|
|
463
|
+
taskId: task.id,
|
|
464
|
+
taskPath,
|
|
465
|
+
agentId: workload.agentId,
|
|
466
|
+
sessionName,
|
|
467
|
+
assignedAt: now,
|
|
468
|
+
};
|
|
469
|
+
// Emit event
|
|
470
|
+
this.emitEvent({
|
|
471
|
+
type: 'task_assigned',
|
|
472
|
+
agentId: workload.agentId,
|
|
473
|
+
sessionName,
|
|
474
|
+
taskId: task.id,
|
|
475
|
+
timestamp: now,
|
|
476
|
+
metadata: {
|
|
477
|
+
taskPath,
|
|
478
|
+
taskTitle: task.title,
|
|
479
|
+
priority: task.priority,
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
// Refresh queue to reflect assignment
|
|
483
|
+
await this.refreshQueue(projectPath);
|
|
484
|
+
this.logger.info('Task assigned', {
|
|
485
|
+
taskId: task.id,
|
|
486
|
+
sessionName,
|
|
487
|
+
taskTitle: task.title,
|
|
488
|
+
});
|
|
489
|
+
return result;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Get the task queue for a project
|
|
493
|
+
*
|
|
494
|
+
* @param projectPath - Path to the project
|
|
495
|
+
* @returns Task queue
|
|
496
|
+
*/
|
|
497
|
+
async getTaskQueue(projectPath) {
|
|
498
|
+
if (!this.queues.has(projectPath)) {
|
|
499
|
+
await this.refreshQueue(projectPath);
|
|
500
|
+
}
|
|
501
|
+
return this.queues.get(projectPath);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Refresh the task queue from the filesystem
|
|
505
|
+
*
|
|
506
|
+
* @param projectPath - Path to the project
|
|
507
|
+
*/
|
|
508
|
+
async refreshQueue(projectPath) {
|
|
509
|
+
const taskService = this.getTaskService(projectPath);
|
|
510
|
+
const allTasks = await taskService.getAllTasks();
|
|
511
|
+
// Get open tasks
|
|
512
|
+
const openTasks = allTasks.filter((t) => t.status === 'open');
|
|
513
|
+
// Get in-progress task IDs to check dependencies
|
|
514
|
+
const inProgressTasks = allTasks.filter((t) => t.status === 'in_progress');
|
|
515
|
+
const inProgressIds = new Set(inProgressTasks.map((t) => t.id));
|
|
516
|
+
const openIds = new Set(openTasks.map((t) => t.id));
|
|
517
|
+
// Build queue
|
|
518
|
+
const queuedTasks = openTasks.map((task) => {
|
|
519
|
+
// Parse dependencies from task content if available
|
|
520
|
+
const dependencies = this.extractDependencies(task);
|
|
521
|
+
// Calculate blocked by - tasks that are dependencies and not yet done
|
|
522
|
+
const blockedBy = dependencies.filter((d) => inProgressIds.has(d) || openIds.has(d));
|
|
523
|
+
return {
|
|
524
|
+
taskId: task.id,
|
|
525
|
+
taskPath: task.filePath,
|
|
526
|
+
title: task.title,
|
|
527
|
+
priority: this.getPriorityValue(task.priority),
|
|
528
|
+
taskType: this.extractTaskType(task),
|
|
529
|
+
requiredRole: task.assignee,
|
|
530
|
+
dependencies,
|
|
531
|
+
blockedBy,
|
|
532
|
+
createdAt: task.createdAt,
|
|
533
|
+
estimatedHours: this.extractEstimatedHours(task),
|
|
534
|
+
labels: this.extractLabels(task),
|
|
535
|
+
};
|
|
536
|
+
});
|
|
537
|
+
// Get assignment history
|
|
538
|
+
const assignments = this.assignmentHistory.get(projectPath) || [];
|
|
539
|
+
const config = await this.getConfig(projectPath);
|
|
540
|
+
this.queues.set(projectPath, {
|
|
541
|
+
projectPath,
|
|
542
|
+
tasks: queuedTasks,
|
|
543
|
+
assignments,
|
|
544
|
+
config,
|
|
545
|
+
lastUpdated: new Date().toISOString(),
|
|
546
|
+
});
|
|
547
|
+
this.logger.debug('Queue refreshed', {
|
|
548
|
+
projectPath,
|
|
549
|
+
taskCount: queuedTasks.length,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Convert priority string to numeric value
|
|
554
|
+
*
|
|
555
|
+
* @param priority - Priority string
|
|
556
|
+
* @returns Numeric priority value
|
|
557
|
+
*/
|
|
558
|
+
getPriorityValue(priority) {
|
|
559
|
+
const values = {
|
|
560
|
+
critical: AUTO_ASSIGN_CONSTANTS.PRIORITY.CRITICAL,
|
|
561
|
+
high: AUTO_ASSIGN_CONSTANTS.PRIORITY.HIGH,
|
|
562
|
+
medium: AUTO_ASSIGN_CONSTANTS.PRIORITY.MEDIUM,
|
|
563
|
+
low: AUTO_ASSIGN_CONSTANTS.PRIORITY.LOW,
|
|
564
|
+
backlog: AUTO_ASSIGN_CONSTANTS.PRIORITY.BACKLOG,
|
|
565
|
+
};
|
|
566
|
+
return values[priority.toLowerCase()] ?? AUTO_ASSIGN_CONSTANTS.PRIORITY.MEDIUM;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Extract task type from task
|
|
570
|
+
*
|
|
571
|
+
* @param task - Task object
|
|
572
|
+
* @returns Task type or undefined
|
|
573
|
+
*/
|
|
574
|
+
extractTaskType(task) {
|
|
575
|
+
// Try to infer from title or labels
|
|
576
|
+
const title = task.title.toLowerCase();
|
|
577
|
+
const taskTypes = Object.values(AUTO_ASSIGN_CONSTANTS.TASK_TYPES).flat();
|
|
578
|
+
for (const type of taskTypes) {
|
|
579
|
+
if (title.includes(type)) {
|
|
580
|
+
return type;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Extract dependencies from task
|
|
587
|
+
*
|
|
588
|
+
* @param task - Task object
|
|
589
|
+
* @returns Array of dependency task IDs
|
|
590
|
+
*/
|
|
591
|
+
extractDependencies(task) {
|
|
592
|
+
// Look for dependency patterns in description
|
|
593
|
+
const depPattern = /depends on:?\s*([^\n]+)/i;
|
|
594
|
+
const match = task.description.match(depPattern);
|
|
595
|
+
if (match) {
|
|
596
|
+
return match[1]
|
|
597
|
+
.split(/[,\s]+/)
|
|
598
|
+
.map((d) => d.trim())
|
|
599
|
+
.filter((d) => d.length > 0);
|
|
600
|
+
}
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Extract estimated hours from task
|
|
605
|
+
*
|
|
606
|
+
* @param task - Task object
|
|
607
|
+
* @returns Estimated hours or undefined
|
|
608
|
+
*/
|
|
609
|
+
extractEstimatedHours(task) {
|
|
610
|
+
const estPattern = /estimated:?\s*(\d+)\s*(hours?|h)/i;
|
|
611
|
+
const match = task.description.match(estPattern);
|
|
612
|
+
if (match) {
|
|
613
|
+
return parseInt(match[1], 10);
|
|
614
|
+
}
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Extract labels from task
|
|
619
|
+
*
|
|
620
|
+
* @param task - Task object
|
|
621
|
+
* @returns Array of labels
|
|
622
|
+
*/
|
|
623
|
+
extractLabels(task) {
|
|
624
|
+
// Look for label patterns
|
|
625
|
+
const labelPattern = /labels?:?\s*([^\n]+)/i;
|
|
626
|
+
const match = task.description.match(labelPattern);
|
|
627
|
+
if (match) {
|
|
628
|
+
return match[1]
|
|
629
|
+
.split(/[,\s]+/)
|
|
630
|
+
.map((l) => l.trim())
|
|
631
|
+
.filter((l) => l.length > 0);
|
|
632
|
+
}
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Get an agent's current workload
|
|
637
|
+
*
|
|
638
|
+
* @param sessionName - Agent session name
|
|
639
|
+
* @returns Agent workload information
|
|
640
|
+
*/
|
|
641
|
+
async getAgentWorkload(sessionName) {
|
|
642
|
+
const trackingService = this.getTaskTrackingService();
|
|
643
|
+
const tasks = await trackingService.getAllInProgressTasks() || [];
|
|
644
|
+
// Filter tasks for this agent
|
|
645
|
+
const agentTasks = tasks.filter((t) => t.assignedSessionName === sessionName &&
|
|
646
|
+
(t.status === 'assigned' || t.status === 'active'));
|
|
647
|
+
// Calculate completed today
|
|
648
|
+
const today = new Date().toISOString().split('T')[0];
|
|
649
|
+
const dailyRecord = this.dailyAssignments.get(sessionName);
|
|
650
|
+
const completedToday = dailyRecord?.date === today ? dailyRecord.count : 0;
|
|
651
|
+
return {
|
|
652
|
+
sessionName,
|
|
653
|
+
agentId: agentTasks[0]?.assignedTeamMemberId || sessionName,
|
|
654
|
+
role: this.getAgentRole(sessionName),
|
|
655
|
+
currentTasks: agentTasks.map((t) => t.id),
|
|
656
|
+
completedToday,
|
|
657
|
+
averageIterations: 0, // Would need iteration tracking to calculate
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get the role for an agent session
|
|
662
|
+
*
|
|
663
|
+
* @param sessionName - Session name
|
|
664
|
+
* @returns Role string
|
|
665
|
+
*/
|
|
666
|
+
getAgentRole(sessionName) {
|
|
667
|
+
// Parse role from session name convention (e.g., "project-developer-1")
|
|
668
|
+
const parts = sessionName.split('-');
|
|
669
|
+
if (parts.length >= 2) {
|
|
670
|
+
// Common role patterns
|
|
671
|
+
const roleKeywords = [
|
|
672
|
+
'developer',
|
|
673
|
+
'frontend-developer',
|
|
674
|
+
'backend-developer',
|
|
675
|
+
'qa',
|
|
676
|
+
'tester',
|
|
677
|
+
'pm',
|
|
678
|
+
'tpm',
|
|
679
|
+
'pgm',
|
|
680
|
+
'designer',
|
|
681
|
+
'orchestrator',
|
|
682
|
+
];
|
|
683
|
+
for (const keyword of roleKeywords) {
|
|
684
|
+
if (sessionName.includes(keyword)) {
|
|
685
|
+
return keyword;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return 'developer'; // Default role
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Register an agent with a project
|
|
693
|
+
*
|
|
694
|
+
* @param sessionName - Agent session name
|
|
695
|
+
* @param projectPath - Project path
|
|
696
|
+
*/
|
|
697
|
+
registerAgent(sessionName, projectPath) {
|
|
698
|
+
this.agentProjects.set(sessionName, projectPath);
|
|
699
|
+
this.logger.debug('Agent registered', { sessionName, projectPath });
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Unregister an agent
|
|
703
|
+
*
|
|
704
|
+
* @param sessionName - Agent session name
|
|
705
|
+
*/
|
|
706
|
+
unregisterAgent(sessionName) {
|
|
707
|
+
this.agentProjects.delete(sessionName);
|
|
708
|
+
this.logger.debug('Agent unregistered', { sessionName });
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Emit an assignment event
|
|
712
|
+
*
|
|
713
|
+
* @param event - Event to emit
|
|
714
|
+
*/
|
|
715
|
+
emitEvent(event) {
|
|
716
|
+
this.emit(event.type, event);
|
|
717
|
+
this.emit('assignment_event', event);
|
|
718
|
+
this.logger.debug('Event emitted', { type: event.type, taskId: event.taskId });
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Register a handler for task assignment events
|
|
722
|
+
*
|
|
723
|
+
* @param handler - Event handler function
|
|
724
|
+
*/
|
|
725
|
+
onTaskAssigned(handler) {
|
|
726
|
+
this.on('task_assigned', handler);
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Register a handler for agent idle events
|
|
730
|
+
*
|
|
731
|
+
* @param handler - Event handler function
|
|
732
|
+
*/
|
|
733
|
+
onAgentIdle(handler) {
|
|
734
|
+
this.on('agent_idle', handler);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Subscribe to task tracking events for a project
|
|
738
|
+
*
|
|
739
|
+
* @param projectPath - Project path
|
|
740
|
+
*/
|
|
741
|
+
subscribeToEvents(projectPath) {
|
|
742
|
+
const trackingService = this.getTaskTrackingService();
|
|
743
|
+
// Listen for task completions
|
|
744
|
+
trackingService.on('task_completed', async (task) => {
|
|
745
|
+
const sessionName = task.assignedSessionName;
|
|
746
|
+
const agentProjectPath = this.agentProjects.get(sessionName);
|
|
747
|
+
if (agentProjectPath === projectPath) {
|
|
748
|
+
// Update assignment history
|
|
749
|
+
const history = this.assignmentHistory.get(projectPath) || [];
|
|
750
|
+
const assignment = history.find((a) => a.taskId === task.id && a.status === 'active');
|
|
751
|
+
if (assignment) {
|
|
752
|
+
assignment.status = 'completed';
|
|
753
|
+
assignment.completedAt = new Date().toISOString();
|
|
754
|
+
}
|
|
755
|
+
// Emit completion event
|
|
756
|
+
this.emitEvent({
|
|
757
|
+
type: 'task_completed',
|
|
758
|
+
agentId: task.assignedTeamMemberId,
|
|
759
|
+
sessionName,
|
|
760
|
+
taskId: task.id,
|
|
761
|
+
timestamp: new Date().toISOString(),
|
|
762
|
+
});
|
|
763
|
+
// Try to assign next task
|
|
764
|
+
await this.assignNextTask(sessionName);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Pause auto-assignment for a project
|
|
770
|
+
*
|
|
771
|
+
* @param projectPath - Path to the project
|
|
772
|
+
*/
|
|
773
|
+
async pauseAutoAssign(projectPath) {
|
|
774
|
+
this.paused.add(projectPath);
|
|
775
|
+
this.logger.info('Auto-assign paused', { projectPath });
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Resume auto-assignment for a project
|
|
779
|
+
*
|
|
780
|
+
* @param projectPath - Path to the project
|
|
781
|
+
*/
|
|
782
|
+
async resumeAutoAssign(projectPath) {
|
|
783
|
+
this.paused.delete(projectPath);
|
|
784
|
+
this.logger.info('Auto-assign resumed', { projectPath });
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Check if auto-assignment is enabled for a project
|
|
788
|
+
*
|
|
789
|
+
* @param projectPath - Path to the project
|
|
790
|
+
* @returns True if enabled and not paused
|
|
791
|
+
*/
|
|
792
|
+
async isAutoAssignEnabled(projectPath) {
|
|
793
|
+
if (this.paused.has(projectPath)) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
const config = this.configs.get(projectPath);
|
|
797
|
+
return config?.enabled ?? false;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Mark a task as failed
|
|
801
|
+
*
|
|
802
|
+
* @param taskId - Task ID
|
|
803
|
+
* @param sessionName - Agent session name
|
|
804
|
+
* @param reason - Failure reason
|
|
805
|
+
*/
|
|
806
|
+
async markTaskFailed(taskId, sessionName, reason) {
|
|
807
|
+
const projectPath = this.agentProjects.get(sessionName);
|
|
808
|
+
if (projectPath) {
|
|
809
|
+
const history = this.assignmentHistory.get(projectPath) || [];
|
|
810
|
+
const assignment = history.find((a) => a.taskId === taskId && a.status === 'active');
|
|
811
|
+
if (assignment) {
|
|
812
|
+
assignment.status = 'failed';
|
|
813
|
+
}
|
|
814
|
+
this.emitEvent({
|
|
815
|
+
type: 'task_failed',
|
|
816
|
+
agentId: assignment?.agentId || sessionName,
|
|
817
|
+
sessionName,
|
|
818
|
+
taskId,
|
|
819
|
+
timestamp: new Date().toISOString(),
|
|
820
|
+
metadata: { reason },
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Get assignment statistics for a project
|
|
826
|
+
*
|
|
827
|
+
* @param projectPath - Project path
|
|
828
|
+
* @returns Assignment statistics
|
|
829
|
+
*/
|
|
830
|
+
getStatistics(projectPath) {
|
|
831
|
+
const history = this.assignmentHistory.get(projectPath) || [];
|
|
832
|
+
return {
|
|
833
|
+
totalAssigned: history.length,
|
|
834
|
+
completed: history.filter((a) => a.status === 'completed').length,
|
|
835
|
+
failed: history.filter((a) => a.status === 'failed').length,
|
|
836
|
+
active: history.filter((a) => a.status === 'active').length,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Clear all cached data (for testing)
|
|
841
|
+
*/
|
|
842
|
+
clearCache() {
|
|
843
|
+
this.configs.clear();
|
|
844
|
+
this.queues.clear();
|
|
845
|
+
this.paused.clear();
|
|
846
|
+
this.agentProjects.clear();
|
|
847
|
+
this.assignmentHistory.clear();
|
|
848
|
+
this.dailyAssignments.clear();
|
|
849
|
+
this.lastAssignmentTime.clear();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
//# sourceMappingURL=auto-assign.service.js.map
|