@task-shepherd/agent 1.0.6 → 1.0.8
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/dist/cli/index.js +1054 -86
- package/dist/index.js +1 -1
- package/dist/meta.json +358 -67
- package/package.json +2 -2
- package/shared/dist/index.d.ts +15 -0
- package/shared/dist/index.js +12 -0
- package/shared/dist/mcp-client/client.d.ts +18 -0
- package/shared/dist/mcp-client/client.js +49 -0
- package/shared/dist/mcp-client/index.d.ts +7 -0
- package/shared/dist/mcp-client/index.js +7 -0
- package/shared/dist/mcp-client/types.d.ts +822 -0
- package/shared/dist/mcp-client/types.js +193 -0
- package/shared/dist/schema/index.d.ts +189 -0
- package/shared/dist/schema/index.js +142 -0
- package/shared/dist/schema/mcp-mappings.d.ts +50 -0
- package/shared/dist/schema/mcp-mappings.js +563 -0
- package/shared/dist/schema/validation.d.ts +91 -0
- package/shared/dist/schema/validation.js +282 -0
- package/shared/dist/work-queue/index.d.ts +7 -0
- package/shared/dist/work-queue/index.js +7 -0
- package/shared/dist/work-queue/types.d.ts +147 -0
- package/shared/dist/work-queue/types.js +4 -0
- package/shared/dist/work-queue/validation.d.ts +24 -0
- package/shared/dist/work-queue/validation.js +160 -0
- package/shared/dist/workspace/constants.d.ts +148 -0
- package/shared/dist/workspace/constants.js +432 -0
- package/shared/dist/workspace/index.d.ts +10 -0
- package/shared/dist/workspace/index.js +10 -0
- package/shared/dist/workspace/types.d.ts +477 -0
- package/shared/dist/workspace/types.js +9 -0
- package/shared/dist/workspace/utils.d.ts +79 -0
- package/shared/dist/workspace/utils.js +334 -0
- package/shared/dist/workspace/validation.d.ts +1312 -0
- package/shared/dist/workspace/validation.js +467 -0
- package/shared/graphql/generated-internal.ts +3629 -0
- package/shared/graphql/generated-public.ts +773 -0
- package/shared/graphql/generated.d.ts +7456 -0
- package/shared/graphql/generated.js +11799 -0
- package/shared/graphql/generated.ts +27569 -0
- package/shared/graphql/generated.ts.backup +16531 -0
- package/shared/graphql/generated.ts.working +4828 -0
- package/shared/graphql/introspection-internal.json +15845 -0
- package/shared/graphql/introspection-public.json +9658 -0
- package/shared/graphql/introspection.json +44263 -0
- package/shared/graphql/operations/ai-service.graphql +131 -0
- package/shared/graphql/operations/ai-work-queue.graphql +31 -0
- package/shared/graphql/operations/analytics.graphql +283 -0
- package/shared/graphql/operations/analytics.ts +3 -0
- package/shared/graphql/operations/api-keys.graphql +126 -0
- package/shared/graphql/operations/attachments.graphql +53 -0
- package/shared/graphql/operations/attachments.ts +39 -0
- package/shared/graphql/operations/audit.graphql +46 -0
- package/shared/graphql/operations/auth.graphql +83 -0
- package/shared/graphql/operations/claude-usage.graphql +178 -0
- package/shared/graphql/operations/comments.graphql +4 -0
- package/shared/graphql/operations/dashboard.graphql +29 -0
- package/shared/graphql/operations/development-plans.graphql +408 -0
- package/shared/graphql/operations/early-access.graphql.disabled +21 -0
- package/shared/graphql/operations/errors.graphql.disabled +83 -0
- package/shared/graphql/operations/internal-api.graphql +931 -0
- package/shared/graphql/operations/notifications.graphql +4 -0
- package/shared/graphql/operations/organization-invites.graphql.disabled +32 -0
- package/shared/graphql/operations/performance.graphql +4 -0
- package/shared/graphql/operations/project-reviews.graphql +610 -0
- package/shared/graphql/operations/projects.graphql +98 -0
- package/shared/graphql/operations/settings.graphql +4 -0
- package/shared/graphql/operations/stories.graphql +113 -0
- package/shared/graphql/operations/subscriptions.graphql +235 -0
- package/shared/graphql/operations/subscriptions.graphql.disabled +96 -0
- package/shared/graphql/operations/tasks.graphql +257 -0
- package/shared/graphql/operations/team.graphql +111 -0
- package/shared/graphql/operations/team.ts +226 -0
- package/shared/graphql/operations/time-tracking.graphql.disabled +96 -0
- package/shared/graphql/operations/work-queue.graphql +210 -0
- package/shared/graphql/operations/work-queue.graphql.disabled +474 -0
- package/shared/graphql/operations/workspace.graphql +146 -0
- package/shared/graphql/schema-internal.graphql +1085 -0
- package/shared/graphql/schema-public.graphql +709 -0
- package/shared/graphql/schema.graphql +3473 -0
- 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,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,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
|
+
}
|