@taodigital/agents 0.0.0-stage → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -2
- package/dist/api-test-agent.d.ts +126 -0
- package/dist/api-test-agent.js +267 -0
- package/dist/artifact-intel-agent.d.ts +11 -0
- package/dist/artifact-intel-agent.js +152 -0
- package/dist/coverage-gap-agent.d.ts +25 -0
- package/dist/coverage-gap-agent.js +269 -0
- package/dist/coverage-map-agent.d.ts +15 -0
- package/dist/coverage-map-agent.js +255 -0
- package/dist/crawl-diff-agent.d.ts +27 -0
- package/dist/crawl-diff-agent.js +412 -0
- package/dist/data-agent.d.ts +101 -0
- package/dist/data-agent.js +2284 -0
- package/dist/document-agent.d.ts +10 -0
- package/dist/document-agent.js +32 -0
- package/dist/enhancement-agent.d.ts +48 -0
- package/dist/enhancement-agent.js +342 -0
- package/dist/field-detector-agent.d.ts +35 -0
- package/dist/field-detector-agent.js +220 -0
- package/dist/flow-agent.d.ts +130 -0
- package/dist/flow-agent.js +169 -0
- package/dist/github-agent.d.ts +18 -0
- package/dist/github-agent.js +39 -0
- package/dist/github-write-agent.d.ts +67 -0
- package/dist/github-write-agent.js +249 -0
- package/dist/grouping-agent.d.ts +18 -0
- package/dist/grouping-agent.js +338 -0
- package/dist/healing-agent.d.ts +73 -0
- package/dist/healing-agent.js +137 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +67 -0
- package/dist/integration-test-agent.d.ts +118 -0
- package/dist/integration-test-agent.js +165 -0
- package/dist/label-assignment-agent.d.ts +15 -0
- package/dist/label-assignment-agent.js +97 -0
- package/dist/llm-config.d.ts +38 -0
- package/dist/llm-config.js +287 -0
- package/dist/llm-mock.d.ts +77 -0
- package/dist/llm-mock.js +192 -0
- package/dist/llm.d.ts +58 -0
- package/dist/llm.js +438 -0
- package/dist/nlp-agent.d.ts +208 -0
- package/dist/nlp-agent.js +340 -0
- package/dist/orchestrator.d.ts +285 -0
- package/dist/orchestrator.js +141 -0
- package/dist/plan-agent.d.ts +4 -0
- package/dist/plan-agent.js +200 -0
- package/dist/repo-scan-agent.d.ts +189 -0
- package/dist/repo-scan-agent.js +2032 -0
- package/dist/requirements-agent.d.ts +59 -0
- package/dist/requirements-agent.js +641 -0
- package/dist/scenario-agent.d.ts +20 -0
- package/dist/scenario-agent.js +123 -0
- package/dist/script-agent.d.ts +447 -0
- package/dist/script-agent.js +11813 -0
- package/dist/security-test-agent.d.ts +111 -0
- package/dist/security-test-agent.js +141 -0
- package/dist/skill-file-agent.d.ts +264 -0
- package/dist/skill-file-agent.js +3470 -0
- package/dist/step-coverage.d.ts +46 -0
- package/dist/step-coverage.js +288 -0
- package/dist/step-utils.d.ts +2 -0
- package/dist/step-utils.js +62 -0
- package/dist/test-case-agent.d.ts +206 -0
- package/dist/test-case-agent.js +2141 -0
- package/dist/test-import-agent.d.ts +148 -0
- package/dist/test-import-agent.js +3235 -0
- package/dist/test-script-agent.d.ts +16 -0
- package/dist/test-script-agent.js +48 -0
- package/dist/token-pricing.d.ts +14 -0
- package/dist/token-pricing.js +62 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.js +7 -0
- package/dist/visual-agent.d.ts +114 -0
- package/dist/visual-agent.js +261 -0
- package/package.json +26 -4
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ZeTa-AI Visual Intelligence Agent
|
|
4
|
+
*
|
|
5
|
+
* Handles visual regression testing:
|
|
6
|
+
* 1. On first crawl — save screenshots as baselines.
|
|
7
|
+
* 2. On subsequent crawls — compare new screenshots to baselines.
|
|
8
|
+
* 3. AI analyzes diffs: meaningful change vs. rendering noise.
|
|
9
|
+
* 4. Generates bug report drafts for critical visual changes.
|
|
10
|
+
*
|
|
11
|
+
* Phase 2: Vision AI integration (uses Claude's vision capability).
|
|
12
|
+
* Phase 1: Pixel-diff fallback (no vision AI needed yet).
|
|
13
|
+
*/
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.setVisualBaseline = setVisualBaseline;
|
|
19
|
+
exports.compareToBaseline = compareToBaseline;
|
|
20
|
+
exports.updateIgnoredRegions = updateIgnoredRegions;
|
|
21
|
+
exports.setAllViewportBaselines = setAllViewportBaselines;
|
|
22
|
+
exports.compareAllViewports = compareAllViewports;
|
|
23
|
+
exports.aiVisualCompare = aiVisualCompare;
|
|
24
|
+
const app_brain_1 = require("@taodigital/app-brain");
|
|
25
|
+
const core_1 = require("@taodigital/core");
|
|
26
|
+
const llm_js_1 = require("./llm.js");
|
|
27
|
+
const llm_config_js_1 = require("./llm-config.js");
|
|
28
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
29
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
30
|
+
async function compressForVision(buf) {
|
|
31
|
+
const compressed = await (0, sharp_1.default)(buf)
|
|
32
|
+
.resize(768, 512, { fit: 'inside', withoutEnlargement: true })
|
|
33
|
+
.jpeg({ quality: 80, progressive: false })
|
|
34
|
+
.toBuffer();
|
|
35
|
+
return { base64: compressed.toString('base64'), mediaType: 'image/jpeg' };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Set or update the visual baseline for a screen.
|
|
39
|
+
* Called after the first successful crawl screenshot is stored.
|
|
40
|
+
*/
|
|
41
|
+
async function setVisualBaseline(opts) {
|
|
42
|
+
await app_brain_1.AppBrain.saveVisualBaseline(opts.tenantId, opts.projectId, opts.screenId, opts.screenshotPath, opts.viewport ?? 'DESKTOP', opts.approvedBy);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Compare a new screenshot against the stored baseline.
|
|
46
|
+
* Phase 1: returns a structural diff (element count change).
|
|
47
|
+
* Phase 2: Vision AI reads both images and produces rich analysis.
|
|
48
|
+
*/
|
|
49
|
+
async function compareToBaseline(opts) {
|
|
50
|
+
const baseline = await app_brain_1.AppBrain.getVisualBaseline(opts.tenantId, opts.screenId, opts.viewport ?? 'DESKTOP');
|
|
51
|
+
if (!baseline)
|
|
52
|
+
return null; // No baseline yet — this IS the baseline.
|
|
53
|
+
const bl = baseline;
|
|
54
|
+
// Phase 1 fallback: compare file sizes as a rough diff proxy.
|
|
55
|
+
// Phase 2: use Vision AI to compare actual images.
|
|
56
|
+
let diffScore = 0;
|
|
57
|
+
let changeType = 'NONE';
|
|
58
|
+
let changes = [];
|
|
59
|
+
let recommendation = 'APPROVE';
|
|
60
|
+
let aiSummary;
|
|
61
|
+
try {
|
|
62
|
+
const [baselineBuffer, newBuffer] = await Promise.all([
|
|
63
|
+
promises_1.default.readFile(bl.storagePath).catch(() => null),
|
|
64
|
+
promises_1.default.readFile(opts.newScreenshotPath).catch(() => null),
|
|
65
|
+
]);
|
|
66
|
+
if (baselineBuffer && newBuffer) {
|
|
67
|
+
const [baselineImg, newImg] = await Promise.all([
|
|
68
|
+
compressForVision(baselineBuffer),
|
|
69
|
+
compressForVision(newBuffer),
|
|
70
|
+
]);
|
|
71
|
+
const aiResult = await aiVisualCompare({
|
|
72
|
+
baselineImageBase64: baselineImg.base64,
|
|
73
|
+
newImageBase64: newImg.base64,
|
|
74
|
+
imageMediaType: baselineImg.mediaType,
|
|
75
|
+
screenName: opts.screenName,
|
|
76
|
+
screenPurpose: 'Application screen captured during automated crawl',
|
|
77
|
+
tenantId: opts.tenantId,
|
|
78
|
+
projectId: opts.projectId,
|
|
79
|
+
});
|
|
80
|
+
diffScore = aiResult.diffScore;
|
|
81
|
+
changeType = aiResult.changeType;
|
|
82
|
+
changes = aiResult.changes;
|
|
83
|
+
recommendation = aiResult.recommendation;
|
|
84
|
+
aiSummary = aiResult.summary;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// AI comparison failed — fall back to file-size proxy
|
|
89
|
+
try {
|
|
90
|
+
const [baselineStat, newStat] = await Promise.all([
|
|
91
|
+
promises_1.default.stat(bl.storagePath).catch(() => null),
|
|
92
|
+
promises_1.default.stat(opts.newScreenshotPath).catch(() => null),
|
|
93
|
+
]);
|
|
94
|
+
if (baselineStat && newStat) {
|
|
95
|
+
const sizeDiff = Math.abs(newStat.size - baselineStat.size) / baselineStat.size;
|
|
96
|
+
diffScore = Math.min(100, sizeDiff * 100);
|
|
97
|
+
if (diffScore < 2) {
|
|
98
|
+
changeType = 'NONE';
|
|
99
|
+
recommendation = 'APPROVE';
|
|
100
|
+
}
|
|
101
|
+
else if (diffScore < 15) {
|
|
102
|
+
changeType = 'MINOR';
|
|
103
|
+
recommendation = 'APPROVE';
|
|
104
|
+
changes = [{ description: 'Minor size change', location: 'Unknown', severity: 'Cosmetic' }];
|
|
105
|
+
}
|
|
106
|
+
else if (diffScore < 40) {
|
|
107
|
+
changeType = 'SIGNIFICANT';
|
|
108
|
+
recommendation = 'REVIEW';
|
|
109
|
+
changes = [{ description: 'Significant visual change detected', location: 'Unknown', severity: 'Functional' }];
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
changeType = 'CRITICAL';
|
|
113
|
+
recommendation = 'FLAG';
|
|
114
|
+
changes = [{ description: 'Major visual change', location: 'Unknown', severity: 'Critical' }];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch { /* ignore */ }
|
|
119
|
+
}
|
|
120
|
+
// Save the diff record.
|
|
121
|
+
const bugReportDraft = changeType === 'CRITICAL' || changeType === 'SIGNIFICANT'
|
|
122
|
+
? {
|
|
123
|
+
title: `Visual regression on "${opts.screenName}"`,
|
|
124
|
+
severity: changeType === 'CRITICAL' ? 'High' : 'Medium',
|
|
125
|
+
steps: ['Navigate to the screen', 'Compare current view to baseline screenshot'],
|
|
126
|
+
expected: 'Screen appearance should match the stored baseline.',
|
|
127
|
+
actual: aiSummary || `Screen has changed (diff score: ${diffScore.toFixed(1)}%)`,
|
|
128
|
+
}
|
|
129
|
+
: undefined;
|
|
130
|
+
await app_brain_1.AppBrain.saveVisualDiff(opts.tenantId, {
|
|
131
|
+
baselineId: bl.id,
|
|
132
|
+
newScreenshotPath: opts.newScreenshotPath,
|
|
133
|
+
diffScore,
|
|
134
|
+
changeType,
|
|
135
|
+
changes,
|
|
136
|
+
recommendation,
|
|
137
|
+
bugReportDraft,
|
|
138
|
+
aiSummary,
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
screenId: opts.screenId,
|
|
142
|
+
changeType,
|
|
143
|
+
diffScore,
|
|
144
|
+
changes,
|
|
145
|
+
recommendation,
|
|
146
|
+
bugReportDraft,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Update the ignored regions for a visual baseline.
|
|
151
|
+
* Regions are excluded from AI visual comparison (PHI masking, dynamic content).
|
|
152
|
+
* Each region is a rectangle in original image coordinates (before compression).
|
|
153
|
+
*/
|
|
154
|
+
async function updateIgnoredRegions(opts) {
|
|
155
|
+
await app_brain_1.AppBrain.updateBaselineIgnoredRegions(opts.tenantId, opts.screenId, opts.viewport ?? 'DESKTOP', opts.regions);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Set visual baselines for all standard viewports from a map of viewport→screenshotPath.
|
|
159
|
+
* Call after crawl completes with screenshots at each breakpoint.
|
|
160
|
+
*/
|
|
161
|
+
async function setAllViewportBaselines(opts) {
|
|
162
|
+
await Promise.all(Object.entries(opts.screenshots).map(([viewport, path]) => setVisualBaseline({
|
|
163
|
+
tenantId: opts.tenantId,
|
|
164
|
+
projectId: opts.projectId,
|
|
165
|
+
screenId: opts.screenId,
|
|
166
|
+
screenshotPath: path,
|
|
167
|
+
viewport,
|
|
168
|
+
approvedBy: opts.approvedBy,
|
|
169
|
+
})));
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Compare a screen against baselines for ALL viewports that have baselines stored.
|
|
173
|
+
* Returns a map of viewport → comparison result. Null value = no baseline for that viewport.
|
|
174
|
+
*/
|
|
175
|
+
async function compareAllViewports(opts) {
|
|
176
|
+
const viewports = ['DESKTOP', 'TABLET', 'MOBILE'];
|
|
177
|
+
const results = {};
|
|
178
|
+
await Promise.all(viewports.map(async (viewport) => {
|
|
179
|
+
const screenshotPath = opts.screenshots[viewport];
|
|
180
|
+
if (!screenshotPath) {
|
|
181
|
+
results[viewport] = null;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
results[viewport] = await compareToBaseline({
|
|
185
|
+
tenantId: opts.tenantId,
|
|
186
|
+
projectId: opts.projectId,
|
|
187
|
+
screenId: opts.screenId,
|
|
188
|
+
newScreenshotPath: screenshotPath,
|
|
189
|
+
screenName: `${opts.screenName} (${viewport})`,
|
|
190
|
+
viewport,
|
|
191
|
+
});
|
|
192
|
+
}));
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Phase 2: AI-powered visual comparison using Claude vision.
|
|
197
|
+
* This replaces the file-size proxy with real image understanding.
|
|
198
|
+
*/
|
|
199
|
+
async function aiVisualCompare(opts) {
|
|
200
|
+
const SYSTEM_DEFAULT = `You are ZeTa-AI's Visual Intelligence Agent. Your job is to compare two screenshots of the SAME application screen and identify REAL, VISIBLE differences between them.
|
|
201
|
+
|
|
202
|
+
CRITICAL RULES:
|
|
203
|
+
1. Only report changes you can ACTUALLY SEE in the provided images. NEVER invent or guess changes.
|
|
204
|
+
2. If the images look identical or nearly identical, return changeType: "NONE" with an empty changes array.
|
|
205
|
+
3. Ignore: font anti-aliasing, sub-pixel rendering, JPEG compression artifacts, identical scroll position differences.
|
|
206
|
+
4. Only flag: missing UI elements, layout shifts, color changes, text content changes, error states, broken images.
|
|
207
|
+
5. Be conservative — false positives are worse than false negatives.
|
|
208
|
+
|
|
209
|
+
Return ONLY valid JSON (no markdown, no explanation):
|
|
210
|
+
{
|
|
211
|
+
"changeType": "NONE" | "MINOR" | "SIGNIFICANT" | "CRITICAL",
|
|
212
|
+
"diffScore": <0-100, where 0=pixel-perfect identical, 100=completely different>,
|
|
213
|
+
"changes": [{"description": "<exact visible change>", "location": "<where on screen>", "severity": "COSMETIC" | "MINOR" | "SIGNIFICANT" | "CRITICAL"}],
|
|
214
|
+
"recommendation": "APPROVE" | "REVIEW" | "FLAG" | "BLOCK",
|
|
215
|
+
"summary": "<one sentence: either 'No meaningful changes detected' or brief description of actual changes>"
|
|
216
|
+
}`;
|
|
217
|
+
const user = `Screen: "${opts.screenName}"
|
|
218
|
+
|
|
219
|
+
Image 1 (BASELINE — the reference): provided above as the first image.
|
|
220
|
+
Image 2 (CURRENT — the new capture): provided above as the second image.
|
|
221
|
+
|
|
222
|
+
Compare them carefully. If they look the same, say so. Only list changes you can actually see.`;
|
|
223
|
+
try {
|
|
224
|
+
const t0 = Date.now();
|
|
225
|
+
const llmCfg = opts.tenantId ? await (0, llm_config_js_1.getTaskAgentLLMConfig)(opts.tenantId, 'visualDiff') : undefined;
|
|
226
|
+
const system = await (0, core_1.getAgentPrompt)('visual-diff-analysis', undefined, opts.tenantId) ?? SYSTEM_DEFAULT;
|
|
227
|
+
const mt = opts.imageMediaType ?? 'image/png';
|
|
228
|
+
const images = [
|
|
229
|
+
{ base64: opts.baselineImageBase64, mediaType: mt },
|
|
230
|
+
{ base64: opts.newImageBase64, mediaType: mt },
|
|
231
|
+
];
|
|
232
|
+
const seed = (0, llm_js_1.deterministicSeed)(`visual-diff:${opts.tenantId ?? ''}:${opts.baselineImageBase64.slice(0, 200)}:${opts.newImageBase64.slice(0, 200)}`);
|
|
233
|
+
const { text: raw, usage } = await (0, llm_js_1.askTrackedWithImages)(system, user, images, llmCfg, { temperature: 0, seed });
|
|
234
|
+
const latencyMs = Date.now() - t0;
|
|
235
|
+
if (opts.tenantId) {
|
|
236
|
+
(0, app_brain_1.recordLLMCall)({
|
|
237
|
+
tenantId: opts.tenantId,
|
|
238
|
+
projectId: opts.projectId,
|
|
239
|
+
agentType: 'visual',
|
|
240
|
+
label: 'ai_visual_compare',
|
|
241
|
+
usage,
|
|
242
|
+
latencyMs,
|
|
243
|
+
}).catch(console.error);
|
|
244
|
+
}
|
|
245
|
+
const start = raw.indexOf('{');
|
|
246
|
+
const end = raw.lastIndexOf('}');
|
|
247
|
+
if (start === -1 || end === -1)
|
|
248
|
+
throw new Error('No JSON in response');
|
|
249
|
+
const result = JSON.parse(raw.slice(start, end + 1));
|
|
250
|
+
// Sanitize: drop any change items missing a description
|
|
251
|
+
if (Array.isArray(result.changes)) {
|
|
252
|
+
result.changes = result.changes.filter((c) => c?.description?.trim());
|
|
253
|
+
}
|
|
254
|
+
if (!result.summary)
|
|
255
|
+
result.summary = result.changeType === 'NONE' ? 'No meaningful changes detected.' : '';
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return { changeType: 'NONE', changes: [], recommendation: 'APPROVE', diffScore: 0, summary: 'No meaningful changes detected.' };
|
|
260
|
+
}
|
|
261
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taodigital/agents",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"registry": "https://registry.npmjs.org/"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "tsx --test src/**/*.test.ts",
|
|
17
|
+
"e2e": "tsx scripts/agent-e2e.ts",
|
|
18
|
+
"eval": "tsx eval/run-eval.ts",
|
|
19
|
+
"eval:accept": "tsx eval/run-eval.ts --accept"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@anthropic-ai/sdk": "^0.27.0",
|
|
23
|
+
"@taodigital/app-brain": "*",
|
|
24
|
+
"@taodigital/core": "*",
|
|
25
|
+
"openai": "^4.82.0",
|
|
26
|
+
"sharp": "^0.35.0"
|
|
27
|
+
}
|
|
28
|
+
}
|