@task-shepherd/agent 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/meta.json +5 -5
  4. package/package.json +2 -2
  5. package/shared/dist/index.d.ts +15 -0
  6. package/shared/dist/index.js +12 -0
  7. package/shared/dist/mcp-client/client.d.ts +18 -0
  8. package/shared/dist/mcp-client/client.js +49 -0
  9. package/shared/dist/mcp-client/index.d.ts +7 -0
  10. package/shared/dist/mcp-client/index.js +7 -0
  11. package/shared/dist/mcp-client/types.d.ts +822 -0
  12. package/shared/dist/mcp-client/types.js +193 -0
  13. package/shared/dist/schema/index.d.ts +189 -0
  14. package/shared/dist/schema/index.js +142 -0
  15. package/shared/dist/schema/mcp-mappings.d.ts +50 -0
  16. package/shared/dist/schema/mcp-mappings.js +563 -0
  17. package/shared/dist/schema/validation.d.ts +91 -0
  18. package/shared/dist/schema/validation.js +282 -0
  19. package/shared/dist/work-queue/index.d.ts +7 -0
  20. package/shared/dist/work-queue/index.js +7 -0
  21. package/shared/dist/work-queue/types.d.ts +147 -0
  22. package/shared/dist/work-queue/types.js +4 -0
  23. package/shared/dist/work-queue/validation.d.ts +24 -0
  24. package/shared/dist/work-queue/validation.js +160 -0
  25. package/shared/dist/workspace/constants.d.ts +148 -0
  26. package/shared/dist/workspace/constants.js +432 -0
  27. package/shared/dist/workspace/index.d.ts +10 -0
  28. package/shared/dist/workspace/index.js +10 -0
  29. package/shared/dist/workspace/types.d.ts +477 -0
  30. package/shared/dist/workspace/types.js +9 -0
  31. package/shared/dist/workspace/utils.d.ts +79 -0
  32. package/shared/dist/workspace/utils.js +334 -0
  33. package/shared/dist/workspace/validation.d.ts +1312 -0
  34. package/shared/dist/workspace/validation.js +467 -0
  35. package/shared/graphql/generated-internal.ts +3629 -0
  36. package/shared/graphql/generated-public.ts +773 -0
  37. package/shared/graphql/generated.d.ts +7456 -0
  38. package/shared/graphql/generated.js +11799 -0
  39. package/shared/graphql/generated.ts +27569 -0
  40. package/shared/graphql/generated.ts.backup +16531 -0
  41. package/shared/graphql/generated.ts.working +4828 -0
  42. package/shared/graphql/introspection-internal.json +15845 -0
  43. package/shared/graphql/introspection-public.json +9658 -0
  44. package/shared/graphql/introspection.json +44263 -0
  45. package/shared/graphql/operations/ai-service.graphql +131 -0
  46. package/shared/graphql/operations/ai-work-queue.graphql +31 -0
  47. package/shared/graphql/operations/analytics.graphql +283 -0
  48. package/shared/graphql/operations/analytics.ts +3 -0
  49. package/shared/graphql/operations/api-keys.graphql +126 -0
  50. package/shared/graphql/operations/attachments.graphql +53 -0
  51. package/shared/graphql/operations/attachments.ts +39 -0
  52. package/shared/graphql/operations/audit.graphql +46 -0
  53. package/shared/graphql/operations/auth.graphql +83 -0
  54. package/shared/graphql/operations/claude-usage.graphql +178 -0
  55. package/shared/graphql/operations/comments.graphql +4 -0
  56. package/shared/graphql/operations/dashboard.graphql +29 -0
  57. package/shared/graphql/operations/development-plans.graphql +408 -0
  58. package/shared/graphql/operations/early-access.graphql.disabled +21 -0
  59. package/shared/graphql/operations/errors.graphql.disabled +83 -0
  60. package/shared/graphql/operations/internal-api.graphql +931 -0
  61. package/shared/graphql/operations/notifications.graphql +4 -0
  62. package/shared/graphql/operations/organization-invites.graphql.disabled +32 -0
  63. package/shared/graphql/operations/performance.graphql +4 -0
  64. package/shared/graphql/operations/project-reviews.graphql +610 -0
  65. package/shared/graphql/operations/projects.graphql +98 -0
  66. package/shared/graphql/operations/settings.graphql +4 -0
  67. package/shared/graphql/operations/stories.graphql +113 -0
  68. package/shared/graphql/operations/subscriptions.graphql +235 -0
  69. package/shared/graphql/operations/subscriptions.graphql.disabled +96 -0
  70. package/shared/graphql/operations/tasks.graphql +257 -0
  71. package/shared/graphql/operations/team.graphql +111 -0
  72. package/shared/graphql/operations/team.ts +226 -0
  73. package/shared/graphql/operations/time-tracking.graphql.disabled +96 -0
  74. package/shared/graphql/operations/work-queue.graphql +210 -0
  75. package/shared/graphql/operations/work-queue.graphql.disabled +474 -0
  76. package/shared/graphql/operations/workspace.graphql +146 -0
  77. package/shared/graphql/schema-internal.graphql +1085 -0
  78. package/shared/graphql/schema-public.graphql +709 -0
  79. package/shared/graphql/schema.graphql +3473 -0
  80. package/shared/package.json +23 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Schema Validation System
3
+ *
4
+ * This module provides comprehensive validation for GraphQL schema consistency
5
+ * and MCP tool compatibility across all TasqHub components.
6
+ */
7
+ import { introspectionFromSchema } from 'graphql';
8
+ import { MCP_TOOL_MAPPINGS, getMCPToolMapping } from './mcp-mappings.js';
9
+ /**
10
+ * Main schema validator class
11
+ */
12
+ export class SchemaValidator {
13
+ constructor(schema) {
14
+ this.schema = null;
15
+ this.introspection = null;
16
+ if (schema) {
17
+ this.setSchema(schema);
18
+ }
19
+ }
20
+ /**
21
+ * Set the GraphQL schema to validate against
22
+ */
23
+ setSchema(schema) {
24
+ this.schema = schema;
25
+ this.introspection = introspectionFromSchema(schema);
26
+ }
27
+ /**
28
+ * Validate all MCP tool mappings against the current schema
29
+ */
30
+ validateMCPMappings() {
31
+ if (!this.schema || !this.introspection) {
32
+ return {
33
+ valid: false,
34
+ errors: [{
35
+ type: 'INVALID_MAPPING',
36
+ message: 'No GraphQL schema provided for validation',
37
+ severity: 'error'
38
+ }],
39
+ warnings: []
40
+ };
41
+ }
42
+ const errors = [];
43
+ const warnings = [];
44
+ // Validate each tool mapping
45
+ for (const category of MCP_TOOL_MAPPINGS) {
46
+ for (const tool of category.tools) {
47
+ const result = this.validateSingleTool(tool);
48
+ errors.push(...result.errors);
49
+ warnings.push(...result.warnings);
50
+ }
51
+ }
52
+ // Check for unused GraphQL operations
53
+ const usedOperations = new Set(MCP_TOOL_MAPPINGS.flatMap(cat => cat.tools.map(tool => tool.graphqlOperation)));
54
+ const allOperations = this.getAllGraphQLOperations();
55
+ for (const operation of allOperations) {
56
+ if (!usedOperations.has(operation)) {
57
+ warnings.push({
58
+ type: 'UNUSED_OPERATION',
59
+ message: `GraphQL operation '${operation}' is not mapped to any MCP tool`,
60
+ suggestion: `Consider creating an MCP tool mapping for this operation or removing it if unused`
61
+ });
62
+ }
63
+ }
64
+ return {
65
+ valid: errors.filter(e => e.severity === 'error').length === 0,
66
+ errors,
67
+ warnings
68
+ };
69
+ }
70
+ /**
71
+ * Validate a single MCP tool mapping
72
+ */
73
+ validateSingleTool(tool) {
74
+ const errors = [];
75
+ const warnings = [];
76
+ // Check if the GraphQL operation exists
77
+ const operation = this.findGraphQLOperation(tool.graphqlOperation, tool.operationType);
78
+ if (!operation) {
79
+ errors.push({
80
+ type: 'MISSING_OPERATION',
81
+ message: `GraphQL ${tool.operationType} '${tool.graphqlOperation}' not found in schema`,
82
+ toolName: tool.toolName,
83
+ operationName: tool.graphqlOperation,
84
+ severity: 'error'
85
+ });
86
+ return { errors, warnings };
87
+ }
88
+ // Validate input schema compatibility
89
+ if (tool.inputSchema && operation.args) {
90
+ const inputValidation = this.validateInputSchema(tool, operation);
91
+ errors.push(...inputValidation.errors);
92
+ warnings.push(...inputValidation.warnings);
93
+ }
94
+ // Check for deprecated operations
95
+ if (tool.deprecated) {
96
+ warnings.push({
97
+ type: 'DEPRECATED_FIELD',
98
+ message: `MCP tool '${tool.toolName}' is deprecated: ${tool.deprecationReason || 'No reason provided'}`,
99
+ suggestion: 'Consider migrating to a newer tool or removing this mapping'
100
+ });
101
+ }
102
+ return { errors, warnings };
103
+ }
104
+ /**
105
+ * Find a GraphQL operation in the schema
106
+ */
107
+ findGraphQLOperation(operationName, operationType) {
108
+ if (!this.introspection)
109
+ return null;
110
+ const typeMap = this.introspection.data.__schema.types.reduce((map, type) => {
111
+ map[type.name] = type;
112
+ return map;
113
+ }, {});
114
+ let rootType;
115
+ switch (operationType) {
116
+ case 'query':
117
+ rootType = this.introspection.data.__schema.queryType.name;
118
+ break;
119
+ case 'mutation':
120
+ rootType = this.introspection.data.__schema.mutationType?.name;
121
+ break;
122
+ case 'subscription':
123
+ rootType = this.introspection.data.__schema.subscriptionType?.name;
124
+ break;
125
+ default:
126
+ return null;
127
+ }
128
+ if (!rootType || !typeMap[rootType])
129
+ return null;
130
+ const rootTypeFields = typeMap[rootType].fields;
131
+ return rootTypeFields?.find((field) => field.name === operationName);
132
+ }
133
+ /**
134
+ * Validate input schema compatibility
135
+ */
136
+ validateInputSchema(tool, operation) {
137
+ const errors = [];
138
+ const warnings = [];
139
+ // This is a simplified validation - in a real implementation,
140
+ // we would do deep type checking between JSON Schema and GraphQL types
141
+ if (tool.inputSchema?.required) {
142
+ const operationArgs = operation.args?.map((arg) => arg.name) || [];
143
+ for (const requiredField of tool.inputSchema.required) {
144
+ if (!operationArgs.includes(requiredField)) {
145
+ errors.push({
146
+ type: 'TYPE_MISMATCH',
147
+ message: `Required field '${requiredField}' in MCP tool '${tool.toolName}' not found in GraphQL operation '${tool.graphqlOperation}'`,
148
+ toolName: tool.toolName,
149
+ operationName: tool.graphqlOperation,
150
+ fieldPath: requiredField,
151
+ severity: 'error'
152
+ });
153
+ }
154
+ }
155
+ }
156
+ return { errors, warnings };
157
+ }
158
+ /**
159
+ * Get all GraphQL operations from the schema
160
+ */
161
+ getAllGraphQLOperations() {
162
+ if (!this.introspection)
163
+ return [];
164
+ const operations = [];
165
+ const typeMap = this.introspection.data.__schema.types.reduce((map, type) => {
166
+ map[type.name] = type;
167
+ return map;
168
+ }, {});
169
+ // Query operations
170
+ const queryType = this.introspection.data.__schema.queryType?.name;
171
+ if (queryType && typeMap[queryType]) {
172
+ operations.push(...typeMap[queryType].fields.map((field) => field.name));
173
+ }
174
+ // Mutation operations
175
+ const mutationType = this.introspection.data.__schema.mutationType?.name;
176
+ if (mutationType && typeMap[mutationType]) {
177
+ operations.push(...typeMap[mutationType].fields.map((field) => field.name));
178
+ }
179
+ // Subscription operations
180
+ const subscriptionType = this.introspection.data.__schema.subscriptionType?.name;
181
+ if (subscriptionType && typeMap[subscriptionType]) {
182
+ operations.push(...typeMap[subscriptionType].fields.map((field) => field.name));
183
+ }
184
+ return operations;
185
+ }
186
+ /**
187
+ * Check compatibility between two schemas (for migration validation)
188
+ */
189
+ static compareSchemas(oldSchema, newSchema) {
190
+ const oldIntrospection = introspectionFromSchema(oldSchema);
191
+ const newIntrospection = introspectionFromSchema(newSchema);
192
+ const breakingChanges = [];
193
+ const safeChanges = [];
194
+ const deprecations = [];
195
+ // This is a simplified implementation - in practice, you'd want to use
196
+ // a library like @graphql-inspector/core for comprehensive schema comparison
197
+ // Compare query operations
198
+ const oldQueries = this.getOperationsFromIntrospection(oldIntrospection, 'query');
199
+ const newQueries = this.getOperationsFromIntrospection(newIntrospection, 'query');
200
+ // Check for removed operations (breaking change)
201
+ for (const oldQuery of oldQueries) {
202
+ if (!newQueries.includes(oldQuery)) {
203
+ breakingChanges.push(`Removed query operation: ${oldQuery}`);
204
+ }
205
+ }
206
+ // Check for added operations (safe change)
207
+ for (const newQuery of newQueries) {
208
+ if (!oldQueries.includes(newQuery)) {
209
+ safeChanges.push(`Added query operation: ${newQuery}`);
210
+ }
211
+ }
212
+ return {
213
+ compatible: breakingChanges.length === 0,
214
+ breakingChanges,
215
+ safeChanges,
216
+ deprecations
217
+ };
218
+ }
219
+ /**
220
+ * Helper to extract operations from introspection
221
+ */
222
+ static getOperationsFromIntrospection(introspection, operationType) {
223
+ const typeMap = introspection.data.__schema.types.reduce((map, type) => {
224
+ map[type.name] = type;
225
+ return map;
226
+ }, {});
227
+ let rootTypeName;
228
+ switch (operationType) {
229
+ case 'query':
230
+ rootTypeName = introspection.data.__schema.queryType?.name;
231
+ break;
232
+ case 'mutation':
233
+ rootTypeName = introspection.data.__schema.mutationType?.name;
234
+ break;
235
+ case 'subscription':
236
+ rootTypeName = introspection.data.__schema.subscriptionType?.name;
237
+ break;
238
+ }
239
+ if (!rootTypeName || !typeMap[rootTypeName])
240
+ return [];
241
+ return typeMap[rootTypeName].fields?.map((field) => field.name) || [];
242
+ }
243
+ /**
244
+ * Validate a specific MCP tool operation
245
+ */
246
+ validateToolOperation(toolName, input) {
247
+ const mapping = getMCPToolMapping(toolName);
248
+ if (!mapping) {
249
+ return { valid: false, errors: [`Unknown MCP tool: ${toolName}`] };
250
+ }
251
+ const errors = [];
252
+ // Validate required fields
253
+ if (mapping.inputSchema?.required) {
254
+ for (const requiredField of mapping.inputSchema.required) {
255
+ if (!(requiredField in input)) {
256
+ errors.push(`Missing required field: ${requiredField}`);
257
+ }
258
+ }
259
+ }
260
+ // Validate permissions (if user context is available)
261
+ // This would be implemented with actual user context in a real application
262
+ return { valid: errors.length === 0, errors };
263
+ }
264
+ }
265
+ /**
266
+ * Utility function to create a schema validator from a GraphQL schema file
267
+ */
268
+ export async function createSchemaValidatorFromFile(schemaPath) {
269
+ // This would read the schema file and parse it
270
+ // For now, returning an empty validator
271
+ return new SchemaValidator();
272
+ }
273
+ /**
274
+ * Utility function to validate schema consistency across components
275
+ */
276
+ export function validateCrossComponentConsistency(schemas) {
277
+ const validator = new SchemaValidator(schemas.backend);
278
+ const result = validator.validateMCPMappings();
279
+ // Additional cross-component validation logic would go here
280
+ // For example, checking that frontend GraphQL operations match backend schema
281
+ return result;
282
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Work Queue Types - Shared between backend and agent
3
+ *
4
+ * Provides consistent contracts for work assignments and AI work operations
5
+ */
6
+ export * from './types.js';
7
+ export * from './validation.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Work Queue Types - Shared between backend and agent
3
+ *
4
+ * Provides consistent contracts for work assignments and AI work operations
5
+ */
6
+ export * from './types.js';
7
+ export * from './validation.js';
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Work Queue Types - Shared between backend and agent
3
+ */
4
+ import { AIWorkStatus } from '../schema/index.js';
5
+ /**
6
+ * Work Assignment structure passed from backend to agent
7
+ */
8
+ export interface WorkAssignment {
9
+ assignmentId: string;
10
+ workerId: string;
11
+ workItemType: string;
12
+ workItemId: string;
13
+ priority: number;
14
+ assignedAt: Date;
15
+ deadline?: Date;
16
+ workData?: WorkData;
17
+ organizationId?: string;
18
+ organizationApiUrl?: string;
19
+ organizationApiKey?: string;
20
+ }
21
+ /**
22
+ * Work data structure containing work-specific information
23
+ */
24
+ export interface WorkData {
25
+ workType: string;
26
+ projectId?: string;
27
+ projectName?: string;
28
+ project?: ProjectData;
29
+ storyId?: string;
30
+ storyTitle?: string;
31
+ story?: StoryData;
32
+ reviewId?: string;
33
+ applicationDetails?: ReviewApplicationDetails;
34
+ applicationPrompt?: string;
35
+ focusAreas?: string[];
36
+ customInstructions?: string;
37
+ organizationId?: string;
38
+ organizationApiUrl?: string;
39
+ organizationApiKey?: string;
40
+ progress?: WorkProgress;
41
+ lastError?: string;
42
+ attemptCount?: number;
43
+ maxRetryAttempts?: number;
44
+ createdAt?: string;
45
+ claimedAt?: string;
46
+ startedAt?: string;
47
+ completedAt?: string;
48
+ status?: string;
49
+ priority?: number;
50
+ aiWorkerId?: string;
51
+ metadata?: Record<string, any>;
52
+ developmentPlanId?: string;
53
+ originalPlanId?: string;
54
+ isReprompt?: boolean;
55
+ repromptContext?: string;
56
+ previousReviewId?: string;
57
+ }
58
+ /**
59
+ * Project data structure
60
+ */
61
+ export interface ProjectData {
62
+ id: string;
63
+ name: string;
64
+ description?: string;
65
+ repositoryUrl?: string;
66
+ workspaceConfig?: WorkspaceConfigData;
67
+ workspaces?: WorkspaceData[];
68
+ }
69
+ /**
70
+ * Story data structure
71
+ */
72
+ export interface StoryData {
73
+ id: string;
74
+ title: string;
75
+ description?: string;
76
+ project?: ProjectData;
77
+ }
78
+ /**
79
+ * Workspace configuration data
80
+ */
81
+ export interface WorkspaceConfigData {
82
+ workspace?: {
83
+ paths?: {
84
+ root?: string;
85
+ };
86
+ };
87
+ [key: string]: any;
88
+ }
89
+ /**
90
+ * Workspace data structure
91
+ */
92
+ export interface WorkspaceData {
93
+ id?: string;
94
+ name?: string;
95
+ path?: string;
96
+ [key: string]: any;
97
+ }
98
+ /**
99
+ * Work progress structure
100
+ */
101
+ export interface WorkProgress {
102
+ assignmentId: string;
103
+ stage: string;
104
+ progressPercentage: number;
105
+ currentStep: string;
106
+ message: string;
107
+ updatedAt: Date;
108
+ estimatedCompletion?: Date;
109
+ metadata?: Record<string, any>;
110
+ }
111
+ /**
112
+ * Review application details
113
+ */
114
+ export interface ReviewApplicationDetails {
115
+ reviewId?: string;
116
+ applicationPrompt?: string;
117
+ focusAreas?: string[];
118
+ customInstructions?: string;
119
+ [key: string]: any;
120
+ }
121
+ /**
122
+ * GraphQL UpdateAIWorkInput - matches backend schema exactly
123
+ */
124
+ export interface UpdateAIWorkInput {
125
+ workId: string;
126
+ status?: AIWorkStatus;
127
+ metadataJson?: string;
128
+ priority?: number;
129
+ }
130
+ /**
131
+ * Work validation result
132
+ */
133
+ export interface WorkValidationResult {
134
+ isValid: boolean;
135
+ errors: ValidationError[];
136
+ warnings: ValidationWarning[];
137
+ }
138
+ export interface ValidationError {
139
+ field: string;
140
+ message: string;
141
+ code: string;
142
+ }
143
+ export interface ValidationWarning {
144
+ field: string;
145
+ message: string;
146
+ code: string;
147
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Work Queue Types - Shared between backend and agent
3
+ */
4
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Work Queue Validation - Shared validation utilities
3
+ */
4
+ import { WorkAssignment, WorkData, WorkValidationResult } from './types.js';
5
+ /**
6
+ * Validate work assignment structure
7
+ */
8
+ export declare function validateWorkAssignment(assignment: WorkAssignment): WorkValidationResult;
9
+ /**
10
+ * Validate work data structure
11
+ */
12
+ export declare function validateWorkData(workData: WorkData): WorkValidationResult;
13
+ /**
14
+ * Check if work assignment is expired based on deadline
15
+ */
16
+ export declare function isWorkAssignmentExpired(assignment: WorkAssignment): boolean;
17
+ /**
18
+ * Get work assignment age in milliseconds
19
+ */
20
+ export declare function getWorkAssignmentAge(assignment: WorkAssignment): number;
21
+ /**
22
+ * Check if work assignment is considered stale (older than specified duration)
23
+ */
24
+ export declare function isWorkAssignmentStale(assignment: WorkAssignment, staleThresholdMs?: number): boolean;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Work Queue Validation - Shared validation utilities
3
+ */
4
+ /**
5
+ * Validate work assignment structure
6
+ */
7
+ export function validateWorkAssignment(assignment) {
8
+ const errors = [];
9
+ const warnings = [];
10
+ // Required fields validation
11
+ if (!assignment.assignmentId || assignment.assignmentId.trim() === '') {
12
+ errors.push({
13
+ field: 'assignmentId',
14
+ message: 'Assignment ID is required',
15
+ code: 'MISSING_ASSIGNMENT_ID'
16
+ });
17
+ }
18
+ if (!assignment.workerId || assignment.workerId.trim() === '') {
19
+ errors.push({
20
+ field: 'workerId',
21
+ message: 'Worker ID is required',
22
+ code: 'MISSING_WORKER_ID'
23
+ });
24
+ }
25
+ if (!assignment.workItemType || assignment.workItemType.trim() === '') {
26
+ errors.push({
27
+ field: 'workItemType',
28
+ message: 'Work item type is required',
29
+ code: 'MISSING_WORK_ITEM_TYPE'
30
+ });
31
+ }
32
+ if (!assignment.workItemId || assignment.workItemId.trim() === '') {
33
+ errors.push({
34
+ field: 'workItemId',
35
+ message: 'Work item ID is required',
36
+ code: 'MISSING_WORK_ITEM_ID'
37
+ });
38
+ }
39
+ if (typeof assignment.priority !== 'number' || assignment.priority < 0) {
40
+ errors.push({
41
+ field: 'priority',
42
+ message: 'Priority must be a non-negative number',
43
+ code: 'INVALID_PRIORITY'
44
+ });
45
+ }
46
+ if (!assignment.assignedAt || !(assignment.assignedAt instanceof Date)) {
47
+ errors.push({
48
+ field: 'assignedAt',
49
+ message: 'Assigned date is required and must be a Date object',
50
+ code: 'INVALID_ASSIGNED_DATE'
51
+ });
52
+ }
53
+ // Deadline validation (warning if in the past)
54
+ if (assignment.deadline && assignment.deadline instanceof Date) {
55
+ if (assignment.deadline < new Date()) {
56
+ warnings.push({
57
+ field: 'deadline',
58
+ message: 'Deadline is in the past',
59
+ code: 'PAST_DEADLINE'
60
+ });
61
+ }
62
+ }
63
+ // Work data validation
64
+ if (assignment.workData) {
65
+ const workDataValidation = validateWorkData(assignment.workData);
66
+ errors.push(...workDataValidation.errors);
67
+ warnings.push(...workDataValidation.warnings);
68
+ }
69
+ return {
70
+ isValid: errors.length === 0,
71
+ errors,
72
+ warnings
73
+ };
74
+ }
75
+ /**
76
+ * Validate work data structure
77
+ */
78
+ export function validateWorkData(workData) {
79
+ const errors = [];
80
+ const warnings = [];
81
+ // Work type validation
82
+ if (!workData.workType || workData.workType.trim() === '') {
83
+ errors.push({
84
+ field: 'workType',
85
+ message: 'Work type is required in work data',
86
+ code: 'MISSING_WORK_TYPE'
87
+ });
88
+ }
89
+ // Organization context validation
90
+ if (workData.organizationId && !workData.organizationApiUrl) {
91
+ warnings.push({
92
+ field: 'organizationApiUrl',
93
+ message: 'Organization API URL should be provided when organization ID is set',
94
+ code: 'MISSING_ORG_API_URL'
95
+ });
96
+ }
97
+ if (workData.organizationApiUrl && !workData.organizationApiKey) {
98
+ warnings.push({
99
+ field: 'organizationApiKey',
100
+ message: 'Organization API key should be provided when organization API URL is set',
101
+ code: 'MISSING_ORG_API_KEY'
102
+ });
103
+ }
104
+ // Progress validation
105
+ if (workData.progress) {
106
+ if (workData.progress.progressPercentage < 0 || workData.progress.progressPercentage > 100) {
107
+ errors.push({
108
+ field: 'progress.progressPercentage',
109
+ message: 'Progress percentage must be between 0 and 100',
110
+ code: 'INVALID_PROGRESS_PERCENTAGE'
111
+ });
112
+ }
113
+ }
114
+ // Retry validation
115
+ if (workData.maxRetryAttempts !== undefined && workData.maxRetryAttempts < 0) {
116
+ errors.push({
117
+ field: 'maxRetryAttempts',
118
+ message: 'Max retry attempts must be non-negative',
119
+ code: 'INVALID_MAX_RETRY_ATTEMPTS'
120
+ });
121
+ }
122
+ if (workData.attemptCount !== undefined && workData.attemptCount < 0) {
123
+ errors.push({
124
+ field: 'attemptCount',
125
+ message: 'Attempt count must be non-negative',
126
+ code: 'INVALID_ATTEMPT_COUNT'
127
+ });
128
+ }
129
+ if (workData.attemptCount !== undefined && workData.maxRetryAttempts !== undefined &&
130
+ workData.attemptCount > workData.maxRetryAttempts) {
131
+ warnings.push({
132
+ field: 'attemptCount',
133
+ message: 'Attempt count exceeds maximum retry attempts',
134
+ code: 'EXCEEDED_MAX_RETRIES'
135
+ });
136
+ }
137
+ return {
138
+ isValid: errors.length === 0,
139
+ errors,
140
+ warnings
141
+ };
142
+ }
143
+ /**
144
+ * Check if work assignment is expired based on deadline
145
+ */
146
+ export function isWorkAssignmentExpired(assignment) {
147
+ return assignment.deadline ? assignment.deadline < new Date() : false;
148
+ }
149
+ /**
150
+ * Get work assignment age in milliseconds
151
+ */
152
+ export function getWorkAssignmentAge(assignment) {
153
+ return Date.now() - assignment.assignedAt.getTime();
154
+ }
155
+ /**
156
+ * Check if work assignment is considered stale (older than specified duration)
157
+ */
158
+ export function isWorkAssignmentStale(assignment, staleThresholdMs = 3600000) {
159
+ return getWorkAssignmentAge(assignment) > staleThresholdMs;
160
+ }