@jay-framework/aiditor 0.19.7 → 0.20.0
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/actions/get-contract-inspector-tags.jay-action +9 -0
- package/dist/actions/get-contract-inspector-tags.jay-action.d.ts +6 -0
- package/dist/actions/get-page-assets.jay-action +10 -0
- package/dist/actions/get-page-assets.jay-action.d.ts +7 -0
- package/dist/actions/get-page-meta.jay-action +9 -0
- package/dist/actions/get-page-meta.jay-action.d.ts +6 -0
- package/dist/actions/save-page-assets.jay-action +8 -0
- package/dist/actions/save-page-assets.jay-action.d.ts +5 -0
- package/dist/actions/save-page-meta.jay-action +8 -0
- package/dist/actions/save-page-meta.jay-action.d.ts +5 -0
- package/dist/actions/sync-detected-page-assets.jay-action +12 -0
- package/dist/actions/sync-detected-page-assets.jay-action.d.ts +10 -0
- package/dist/agent-kit-template/plugin/aiditor-add-menu.md +24 -0
- package/dist/index.client.d.ts +296 -34
- package/dist/index.client.js +8606 -2346
- package/dist/index.d.ts +129 -2
- package/dist/index.js +728 -58
- package/dist/pages/aiditor/page.css +1048 -49
- package/dist/pages/aiditor/page.jay-html +2486 -603
- package/dist/pages/aiditor/page.jay-html.d.ts +1013 -0
- package/package.json +18 -9
- package/plugin.yaml +12 -0
package/dist/index.js
CHANGED
|
@@ -5,11 +5,13 @@ import path, { extname } from "path";
|
|
|
5
5
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
6
6
|
import fs$1 from "fs";
|
|
7
7
|
import crypto from "crypto";
|
|
8
|
-
import { load } from "js-yaml";
|
|
8
|
+
import yaml, { load } from "js-yaml";
|
|
9
9
|
import { getLogger } from "@jay-framework/logger";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import { spawn } from "child_process";
|
|
12
12
|
import { scanRoutes } from "@jay-framework/stack-route-scanner";
|
|
13
|
+
import { parseContract, ContractTagType } from "@jay-framework/compiler-jay-html";
|
|
14
|
+
import { checkValidationErrors } from "@jay-framework/compiler-shared";
|
|
13
15
|
import { setActionCallerOptions } from "@jay-framework/stack-client-runtime";
|
|
14
16
|
const getAiditorBootstrap = makeJayQuery(
|
|
15
17
|
"aiditor.getAiditorBootstrap"
|
|
@@ -20,7 +22,7 @@ const getAiditorBootstrap = makeJayQuery(
|
|
|
20
22
|
};
|
|
21
23
|
});
|
|
22
24
|
const getProjectInfoAction = makeJayQuery("aditor.getProjectInfo").withServices(DEV_SERVER_SERVICE).withHandler(async (input, devServer) => {
|
|
23
|
-
const routes = devServer.
|
|
25
|
+
const routes = await devServer.refreshRoutes();
|
|
24
26
|
return { routes, httpBaseUrl: input.jayDevUrl };
|
|
25
27
|
});
|
|
26
28
|
const getPageParamsAction = makeJayStream("aditor.getPageParams").withServices(DEV_SERVER_SERVICE).withHandler(async function* (input, devServer) {
|
|
@@ -367,6 +369,30 @@ function optionalString(obj, field) {
|
|
|
367
369
|
const trimmed = value.trim();
|
|
368
370
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
369
371
|
}
|
|
372
|
+
function optionalBoolean(obj, field) {
|
|
373
|
+
const value = obj[field];
|
|
374
|
+
return typeof value === "boolean" ? value : void 0;
|
|
375
|
+
}
|
|
376
|
+
function validateInteraction(raw, path2, errors) {
|
|
377
|
+
if (raw === void 0) return void 0;
|
|
378
|
+
if (!isRecord(raw)) {
|
|
379
|
+
errors.push({ path: path2, message: "interaction must be an object" });
|
|
380
|
+
return void 0;
|
|
381
|
+
}
|
|
382
|
+
const mode = raw.mode;
|
|
383
|
+
if (mode !== "reference" && mode !== "stage-place") {
|
|
384
|
+
errors.push({
|
|
385
|
+
path: `${path2}.mode`,
|
|
386
|
+
message: 'mode must be "reference" or "stage-place"'
|
|
387
|
+
});
|
|
388
|
+
return void 0;
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
mode,
|
|
392
|
+
persistOnPage: optionalBoolean(raw, "persistOnPage"),
|
|
393
|
+
stagePromptTemplate: optionalString(raw, "stagePromptTemplate")
|
|
394
|
+
};
|
|
395
|
+
}
|
|
370
396
|
function validateAddMenuItem(raw, path2) {
|
|
371
397
|
const errors = [];
|
|
372
398
|
if (!isRecord(raw)) {
|
|
@@ -390,6 +416,11 @@ function validateAddMenuItem(raw, path2) {
|
|
|
390
416
|
if (errors.length > 0 || !id || !title || !category || !prompt) {
|
|
391
417
|
return { item: null, errors };
|
|
392
418
|
}
|
|
419
|
+
const interaction = validateInteraction(
|
|
420
|
+
raw.interaction,
|
|
421
|
+
`${path2}.interaction`,
|
|
422
|
+
errors
|
|
423
|
+
);
|
|
393
424
|
return {
|
|
394
425
|
item: {
|
|
395
426
|
id,
|
|
@@ -399,7 +430,8 @@ function validateAddMenuItem(raw, path2) {
|
|
|
399
430
|
pluginName: optionalString(raw, "pluginName"),
|
|
400
431
|
packageName: optionalString(raw, "packageName"),
|
|
401
432
|
subCategory: optionalString(raw, "subCategory"),
|
|
402
|
-
thumbnail: optionalString(raw, "thumbnail")
|
|
433
|
+
thumbnail: optionalString(raw, "thumbnail"),
|
|
434
|
+
...interaction ? { interaction } : {}
|
|
403
435
|
},
|
|
404
436
|
errors
|
|
405
437
|
};
|
|
@@ -431,11 +463,11 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
|
431
463
|
}
|
|
432
464
|
return { file: { items }, errors };
|
|
433
465
|
}
|
|
434
|
-
function parsePluginsIndexYaml(
|
|
466
|
+
function parsePluginsIndexYaml(yaml2) {
|
|
435
467
|
const entries = [];
|
|
436
468
|
let currentPlugin = null;
|
|
437
469
|
let inContracts = false;
|
|
438
|
-
for (const line of
|
|
470
|
+
for (const line of yaml2.split("\n")) {
|
|
439
471
|
const pluginMatch = line.match(/^ - name:\s*(.+)$/);
|
|
440
472
|
if (pluginMatch && !line.startsWith(" ")) {
|
|
441
473
|
currentPlugin = pluginMatch[1].trim();
|
|
@@ -512,8 +544,8 @@ async function isPluginInstalled(projectRoot, pluginName) {
|
|
|
512
544
|
async function parsePluginsIndexContracts(projectRoot) {
|
|
513
545
|
const indexPath = path.join(projectRoot, "agent-kit", "plugins-index.yaml");
|
|
514
546
|
try {
|
|
515
|
-
const
|
|
516
|
-
return parsePluginsIndexYaml(
|
|
547
|
+
const yaml2 = await fs.readFile(indexPath, "utf-8");
|
|
548
|
+
return parsePluginsIndexYaml(yaml2);
|
|
517
549
|
} catch (e) {
|
|
518
550
|
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
519
551
|
if (code === "ENOENT") return [];
|
|
@@ -529,10 +561,10 @@ async function parsePluginYamlContracts(projectRoot, pluginName) {
|
|
|
529
561
|
"plugin.yaml"
|
|
530
562
|
);
|
|
531
563
|
try {
|
|
532
|
-
const
|
|
564
|
+
const yaml2 = await fs.readFile(pluginYamlPath, "utf-8");
|
|
533
565
|
const entries = [];
|
|
534
566
|
let inContracts = false;
|
|
535
|
-
for (const line of
|
|
567
|
+
for (const line of yaml2.split("\n")) {
|
|
536
568
|
if (line.trim() === "contracts:") {
|
|
537
569
|
inContracts = true;
|
|
538
570
|
continue;
|
|
@@ -638,6 +670,65 @@ async function listAddMenuItems(projectRoot) {
|
|
|
638
670
|
warnings
|
|
639
671
|
};
|
|
640
672
|
}
|
|
673
|
+
const DEFAULT_PUBLIC_FOLDER = "./public";
|
|
674
|
+
function resolveProjectPublicDir(projectDir) {
|
|
675
|
+
const configPath = path.join(projectDir, ".jay");
|
|
676
|
+
let publicFolder = DEFAULT_PUBLIC_FOLDER;
|
|
677
|
+
if (fs$1.existsSync(configPath)) {
|
|
678
|
+
try {
|
|
679
|
+
const parsed = yaml.load(fs$1.readFileSync(configPath, "utf-8"));
|
|
680
|
+
if (parsed?.devServer?.publicFolder) {
|
|
681
|
+
publicFolder = parsed.devServer.publicFolder;
|
|
682
|
+
}
|
|
683
|
+
} catch {
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return path.resolve(projectDir, publicFolder);
|
|
687
|
+
}
|
|
688
|
+
function sanitizeAttachmentFileName(name) {
|
|
689
|
+
const base = path.basename(name).replace(/[^\w.\-]+/g, "_");
|
|
690
|
+
return base.length > 0 ? base : "attachment";
|
|
691
|
+
}
|
|
692
|
+
function projectHasWixMediaPlugin(projectDir) {
|
|
693
|
+
const indexPath = path.join(projectDir, "agent-kit", "plugins-index.yaml");
|
|
694
|
+
if (!fs$1.existsSync(indexPath)) return false;
|
|
695
|
+
try {
|
|
696
|
+
const parsed = yaml.load(fs$1.readFileSync(indexPath, "utf-8"));
|
|
697
|
+
return parsed.plugins?.some((plugin) => plugin.name === "wix-media") ?? false;
|
|
698
|
+
} catch {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function buildVisualAttachmentRules(projectDir) {
|
|
703
|
+
const publicDir = resolveProjectPublicDir(projectDir);
|
|
704
|
+
const publicFolderLabel = path.relative(projectDir, publicDir) || "public";
|
|
705
|
+
const lines = [
|
|
706
|
+
"User-uploaded file attachments (per marker):",
|
|
707
|
+
"- Each attachment is on disk at the listed path — Read it for context (layout, colors, dimensions, inspiration).",
|
|
708
|
+
"- Use the marker instruction to decide whether to place the file in the project:",
|
|
709
|
+
" - Instruction asks to display, embed, show, use, set as image/background, or otherwise wire the attachment into the page → copy the file from the attachment path into the project before adding src/href in jay-html. Do not reference build/aditor paths in templates.",
|
|
710
|
+
' - Attachment is reference or inspiration only (mood, "like this", layout reference) and the instruction does not ask to embed it → read for context only; do not copy into the project.',
|
|
711
|
+
`- Local static hosting: copy into \`${publicFolderLabel}/\` (keep the original filename when safe). Reference in jay-html as \`/{filename}\` (e.g. \`src="/photo.png"\`).`,
|
|
712
|
+
"- Never invent placeholder image URLs."
|
|
713
|
+
];
|
|
714
|
+
if (projectHasWixMediaPlugin(projectDir)) {
|
|
715
|
+
lines.push(
|
|
716
|
+
"- Wix Media Manager (`@jay-framework/wix-media` is installed): when the user wants this image as a live site asset, run `jay-stack run wix-media/upload-public` on the attachment file (or copy to public first if the command expects a project path), then use the returned Wix CDN URL in jay-html. Do not duplicate Wix CDN assets into public/. Read agent-kit/designer/wix-media.md for URL patterns."
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
return lines.join("\n");
|
|
720
|
+
}
|
|
721
|
+
function persistFile$1(file, destDir, fileName) {
|
|
722
|
+
fs$1.mkdirSync(destDir, { recursive: true });
|
|
723
|
+
const dest = path.join(destDir, fileName);
|
|
724
|
+
fs$1.copyFileSync(file.path, dest);
|
|
725
|
+
return dest;
|
|
726
|
+
}
|
|
727
|
+
function persistVisualTaskAttachment(file, taskAttDir) {
|
|
728
|
+
const originalFileName = sanitizeAttachmentFileName(file.name);
|
|
729
|
+
const taskCopyPath = persistFile$1(file, taskAttDir, originalFileName);
|
|
730
|
+
return { taskCopyPath, originalFileName };
|
|
731
|
+
}
|
|
641
732
|
const CATALOG = [
|
|
642
733
|
{
|
|
643
734
|
pluginName: "wix-server-client",
|
|
@@ -814,6 +905,26 @@ function buildLegacyPluginManifestPromptSection(selectedPlugins) {
|
|
|
814
905
|
}
|
|
815
906
|
function buildRouteMigrationPromptSection(routeMigration) {
|
|
816
907
|
if (!routeMigration) return "";
|
|
908
|
+
if (routeMigration.reason === "page-info-route-edit") {
|
|
909
|
+
return [
|
|
910
|
+
"",
|
|
911
|
+
"## Page route change (required — apply now)",
|
|
912
|
+
"",
|
|
913
|
+
`- Current route: \`${routeMigration.currentRoute}\``,
|
|
914
|
+
`- New route: \`${routeMigration.plannedRoute}\``,
|
|
915
|
+
"",
|
|
916
|
+
"The user approved changing this page's URL. Implement the migration now:",
|
|
917
|
+
"- Move or rename `src/pages/` files and directories so the page is served at the new route.",
|
|
918
|
+
"- Jay Stack directory mapping (URL → folder under `src/pages/`):",
|
|
919
|
+
" - `/segment/:param` → `segment/[param]/`",
|
|
920
|
+
" - optional `/{/:param}` (Express) → `segment/[[param]]/` (double brackets)",
|
|
921
|
+
" - catch-all `/*param` → `segment/[...param]/`",
|
|
922
|
+
"- After moving, remove the old `page.jay-html` (and empty parent dirs if safe).",
|
|
923
|
+
"- Update all internal links, navigation, breadcrumbs, and dynamic links across the site that reference the old route.",
|
|
924
|
+
"- Keep headless component bindings and jay-params consistent with the new route shape.",
|
|
925
|
+
""
|
|
926
|
+
].join("\n");
|
|
927
|
+
}
|
|
817
928
|
return [
|
|
818
929
|
"",
|
|
819
930
|
"## Planned route migration (apply only if user request requires it)",
|
|
@@ -826,6 +937,35 @@ function buildRouteMigrationPromptSection(routeMigration) {
|
|
|
826
937
|
""
|
|
827
938
|
].join("\n");
|
|
828
939
|
}
|
|
940
|
+
function buildPageAssetsPromptSection(assets) {
|
|
941
|
+
const active = assets.filter((a) => a.persistOnPage && !a.userRemoved);
|
|
942
|
+
if (active.length === 0) return "";
|
|
943
|
+
const lines = [
|
|
944
|
+
"Page assets (in scope for this page — use when referenced in marker instructions or @mentions):"
|
|
945
|
+
];
|
|
946
|
+
for (const asset of active) {
|
|
947
|
+
lines.push(`- ${asset.title} (id: ${asset.id})`);
|
|
948
|
+
for (const line of asset.prompt.trim().split("\n")) {
|
|
949
|
+
lines.push(` ${line}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return lines.join("\n");
|
|
953
|
+
}
|
|
954
|
+
function buildPageComponentsPromptSection(components) {
|
|
955
|
+
if (components.length === 0) return "";
|
|
956
|
+
const lines = [
|
|
957
|
+
"Page components (from page.jay-html — use when referenced in marker instructions or @mentions):"
|
|
958
|
+
];
|
|
959
|
+
for (const component of components) {
|
|
960
|
+
lines.push(
|
|
961
|
+
`- ${component.title}${component.componentKey ? ` (key: ${component.componentKey})` : ""}`
|
|
962
|
+
);
|
|
963
|
+
for (const line of component.prompt.trim().split("\n")) {
|
|
964
|
+
lines.push(` ${line}`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return lines.join("\n");
|
|
968
|
+
}
|
|
829
969
|
function buildNonVisualPrompt(config, notes, imagePath) {
|
|
830
970
|
const lines = [
|
|
831
971
|
`Project directory: ${config.projectDir}`,
|
|
@@ -914,6 +1054,8 @@ function parseNotesField(notes) {
|
|
|
914
1054
|
annotations: j.annotations,
|
|
915
1055
|
previewNavLog,
|
|
916
1056
|
addMenuAttachments: j.addMenuAttachments,
|
|
1057
|
+
pageAssets: j.pageAssets,
|
|
1058
|
+
pageComponents: j.pageComponents,
|
|
917
1059
|
routeMigration: j.routeMigration
|
|
918
1060
|
},
|
|
919
1061
|
plain: ""
|
|
@@ -926,6 +1068,8 @@ function parseNotesField(notes) {
|
|
|
926
1068
|
structured: {
|
|
927
1069
|
annotations: j.annotations,
|
|
928
1070
|
addMenuAttachments: j.addMenuAttachments,
|
|
1071
|
+
pageAssets: j.pageAssets,
|
|
1072
|
+
pageComponents: j.pageComponents,
|
|
929
1073
|
routeMigration: j.routeMigration
|
|
930
1074
|
},
|
|
931
1075
|
structuredVideo: null,
|
|
@@ -936,6 +1080,26 @@ function parseNotesField(notes) {
|
|
|
936
1080
|
}
|
|
937
1081
|
return empty();
|
|
938
1082
|
}
|
|
1083
|
+
function appendVisualAttachmentLines(body, attachments, indent = "") {
|
|
1084
|
+
if (attachments.length === 0) {
|
|
1085
|
+
body.push(`${indent}Attachments: (none)`);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
body.push(
|
|
1089
|
+
`${indent}Attachments (read from disk; belong to this annotation only):`
|
|
1090
|
+
);
|
|
1091
|
+
for (const att of attachments) {
|
|
1092
|
+
body.push(
|
|
1093
|
+
`${indent}- ${att.taskCopyPath} (filename: ${att.originalFileName})`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
function visualPromptHasAttachments(attachmentMap) {
|
|
1098
|
+
for (const list of attachmentMap.values()) {
|
|
1099
|
+
if (list.length > 0) return true;
|
|
1100
|
+
}
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
939
1103
|
function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, attachmentPathsByAnnotationId, addMenu = { itemById: /* @__PURE__ */ new Map() }) {
|
|
940
1104
|
const preamble = [
|
|
941
1105
|
`Project directory: ${config.projectDir}`,
|
|
@@ -952,9 +1116,20 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
|
|
|
952
1116
|
"- The annotation instructions and attachments are listed below in text — do NOT rely on screenshot text.",
|
|
953
1117
|
"",
|
|
954
1118
|
ANNOTATION_TARGETING_RULES,
|
|
955
|
-
""
|
|
1119
|
+
"",
|
|
1120
|
+
...visualPromptHasAttachments(attachmentPathsByAnnotationId) ? [buildVisualAttachmentRules(config.projectDir), ""] : []
|
|
956
1121
|
];
|
|
957
1122
|
const body = [];
|
|
1123
|
+
const pageComponents = parsed.structured?.pageComponents ?? addMenu.pageComponents ?? [];
|
|
1124
|
+
if (pageComponents.length > 0) {
|
|
1125
|
+
const section = buildPageComponentsPromptSection(pageComponents);
|
|
1126
|
+
if (section.trim()) body.push(section.trim(), "");
|
|
1127
|
+
}
|
|
1128
|
+
const pageAssets = parsed.structured?.pageAssets ?? addMenu.pageAssets ?? [];
|
|
1129
|
+
if (pageAssets.length > 0) {
|
|
1130
|
+
const section = buildPageAssetsPromptSection(pageAssets);
|
|
1131
|
+
if (section.trim()) body.push(section.trim(), "");
|
|
1132
|
+
}
|
|
958
1133
|
const routeMigration = parsed.structured?.routeMigration ?? void 0;
|
|
959
1134
|
const requestAddMenu = parsed.structured?.addMenuAttachments ?? [];
|
|
960
1135
|
if (requestAddMenu.length > 0) {
|
|
@@ -981,16 +1156,7 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
|
|
|
981
1156
|
appendLegacyBindingLines(body, ann, ann.id);
|
|
982
1157
|
}
|
|
983
1158
|
body.push(`Instruction: ${ann.instruction.trim()}`);
|
|
984
|
-
|
|
985
|
-
body.push(
|
|
986
|
-
"Attachments (read these files; they belong to this annotation only):"
|
|
987
|
-
);
|
|
988
|
-
for (const p of paths) {
|
|
989
|
-
body.push(`- ${p}`);
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
body.push("Attachments: (none)");
|
|
993
|
-
}
|
|
1159
|
+
appendVisualAttachmentLines(body, paths);
|
|
994
1160
|
body.push("");
|
|
995
1161
|
}
|
|
996
1162
|
} else {
|
|
@@ -1025,7 +1191,8 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
1025
1191
|
"",
|
|
1026
1192
|
...navLines,
|
|
1027
1193
|
ANNOTATION_TARGETING_RULES,
|
|
1028
|
-
""
|
|
1194
|
+
"",
|
|
1195
|
+
...visualPromptHasAttachments(attachmentPathsByPin) ? [buildVisualAttachmentRules(config.projectDir), ""] : []
|
|
1029
1196
|
];
|
|
1030
1197
|
if (!sv || sv.annotations.length === 0) {
|
|
1031
1198
|
return [
|
|
@@ -1039,6 +1206,16 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
1039
1206
|
pinById.set(a.id, String(i + 1));
|
|
1040
1207
|
}
|
|
1041
1208
|
const body = [];
|
|
1209
|
+
const pageComponents = sv.pageComponents ?? addMenu.pageComponents ?? [];
|
|
1210
|
+
if (pageComponents.length > 0) {
|
|
1211
|
+
const section = buildPageComponentsPromptSection(pageComponents);
|
|
1212
|
+
if (section.trim()) body.push(section.trim(), "");
|
|
1213
|
+
}
|
|
1214
|
+
const pageAssets = sv.pageAssets ?? addMenu.pageAssets ?? [];
|
|
1215
|
+
if (pageAssets.length > 0) {
|
|
1216
|
+
const section = buildPageAssetsPromptSection(pageAssets);
|
|
1217
|
+
if (section.trim()) body.push(section.trim(), "");
|
|
1218
|
+
}
|
|
1042
1219
|
const routeMigration = sv.routeMigration;
|
|
1043
1220
|
const requestAddMenu = sv.addMenuAttachments ?? [];
|
|
1044
1221
|
if (requestAddMenu.length > 0) {
|
|
@@ -1079,10 +1256,7 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
1079
1256
|
body.push(` Page path at this pin: ${ann.pagePathAtTime}`);
|
|
1080
1257
|
}
|
|
1081
1258
|
if (paths.length > 0) {
|
|
1082
|
-
body
|
|
1083
|
-
" Attachments (read these files; they belong to this pin only):"
|
|
1084
|
-
);
|
|
1085
|
-
for (const p of paths) body.push(` - ${p}`);
|
|
1259
|
+
appendVisualAttachmentLines(body, paths, " ");
|
|
1086
1260
|
} else {
|
|
1087
1261
|
body.push(" Attachments: (none)");
|
|
1088
1262
|
}
|
|
@@ -1097,6 +1271,19 @@ function persistFile(file, destDir, fileName) {
|
|
|
1097
1271
|
fs$1.copyFileSync(file.path, dest);
|
|
1098
1272
|
return dest;
|
|
1099
1273
|
}
|
|
1274
|
+
function persistExtraFileAttachments(taskDir, extraFiles) {
|
|
1275
|
+
const attachmentPaths = /* @__PURE__ */ new Map();
|
|
1276
|
+
for (const [key, file] of Object.entries(extraFiles)) {
|
|
1277
|
+
if (!key.startsWith("attachment_")) continue;
|
|
1278
|
+
const pinId = key.split("_")[1];
|
|
1279
|
+
const attDir = path.join(taskDir, `annotation-${pinId}`);
|
|
1280
|
+
const persisted = persistVisualTaskAttachment(file, attDir);
|
|
1281
|
+
const existing = attachmentPaths.get(pinId) ?? [];
|
|
1282
|
+
existing.push(persisted);
|
|
1283
|
+
attachmentPaths.set(pinId, existing);
|
|
1284
|
+
}
|
|
1285
|
+
return attachmentPaths;
|
|
1286
|
+
}
|
|
1100
1287
|
const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHandler(async function* (input) {
|
|
1101
1288
|
const projectDir = process.cwd();
|
|
1102
1289
|
const buildFolder = path.join(projectDir, "build");
|
|
@@ -1110,7 +1297,8 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1110
1297
|
const isVisual = !!input.visualTask;
|
|
1111
1298
|
const catalog = await listAddMenuItems(projectDir);
|
|
1112
1299
|
const addMenu = {
|
|
1113
|
-
itemById: new Map(catalog.items.map((item) => [item.id, item]))
|
|
1300
|
+
itemById: new Map(catalog.items.map((item) => [item.id, item])),
|
|
1301
|
+
pageAssets: parsed.structured?.pageAssets ?? parsed.structuredVideo?.pageAssets ?? void 0
|
|
1114
1302
|
};
|
|
1115
1303
|
let promptContent;
|
|
1116
1304
|
if (isVideo) {
|
|
@@ -1136,16 +1324,7 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1136
1324
|
markersOnly: markers ? persistFile(markers, frameDir, "markers-only.png") : ""
|
|
1137
1325
|
});
|
|
1138
1326
|
}
|
|
1139
|
-
const attachmentPathsByPin =
|
|
1140
|
-
for (const [key, file] of Object.entries(extra)) {
|
|
1141
|
-
if (!key.startsWith("attachment_")) continue;
|
|
1142
|
-
const pinId = key.split("_")[1];
|
|
1143
|
-
const attDir = path.join(taskDir, `annotation-${pinId}`);
|
|
1144
|
-
const filePath = persistFile(file, attDir);
|
|
1145
|
-
const existing = attachmentPathsByPin.get(pinId) ?? [];
|
|
1146
|
-
existing.push(filePath);
|
|
1147
|
-
attachmentPathsByPin.set(pinId, existing);
|
|
1148
|
-
}
|
|
1327
|
+
const attachmentPathsByPin = persistExtraFileAttachments(taskDir, extra);
|
|
1149
1328
|
promptContent = buildVisualPromptVideo(
|
|
1150
1329
|
config,
|
|
1151
1330
|
input.pageRoute ?? "/",
|
|
@@ -1167,16 +1346,10 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1167
1346
|
)
|
|
1168
1347
|
};
|
|
1169
1348
|
const extraFiles = input.extraFiles ?? {};
|
|
1170
|
-
const attachmentPathsByAnnotationId =
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
const attDir = path.join(taskDir, `annotation-${pinId}`);
|
|
1175
|
-
const filePath = persistFile(file, attDir);
|
|
1176
|
-
const existing = attachmentPathsByAnnotationId.get(pinId) ?? [];
|
|
1177
|
-
existing.push(filePath);
|
|
1178
|
-
attachmentPathsByAnnotationId.set(pinId, existing);
|
|
1179
|
-
}
|
|
1349
|
+
const attachmentPathsByAnnotationId = persistExtraFileAttachments(
|
|
1350
|
+
taskDir,
|
|
1351
|
+
extraFiles
|
|
1352
|
+
);
|
|
1180
1353
|
promptContent = buildVisualPromptDual(
|
|
1181
1354
|
config,
|
|
1182
1355
|
input.pageRoute ?? "/",
|
|
@@ -2712,10 +2885,10 @@ function isValidInstallSpec(spec) {
|
|
|
2712
2885
|
function getPluginSourcesPath(projectRoot) {
|
|
2713
2886
|
return path.join(projectRoot, ".aiditor", "plugin-sources.yaml");
|
|
2714
2887
|
}
|
|
2715
|
-
function parsePluginSourcesYaml(
|
|
2888
|
+
function parsePluginSourcesYaml(yaml2) {
|
|
2716
2889
|
const sources = [];
|
|
2717
2890
|
let current = null;
|
|
2718
|
-
for (const line of
|
|
2891
|
+
for (const line of yaml2.split("\n")) {
|
|
2719
2892
|
const itemMatch = line.match(/^ - pluginName:\s*(.+)$/);
|
|
2720
2893
|
if (itemMatch) {
|
|
2721
2894
|
if (current?.pluginName && current.packageName && current.installSpec) {
|
|
@@ -3443,24 +3616,61 @@ function pluginNameFromItemId(itemId) {
|
|
|
3443
3616
|
if (colon <= 0) return null;
|
|
3444
3617
|
return itemId.slice(0, colon);
|
|
3445
3618
|
}
|
|
3446
|
-
async function
|
|
3447
|
-
const
|
|
3448
|
-
const contractName = contractNameFromItemId(item.id);
|
|
3449
|
-
if (!pluginName || !contractName) return null;
|
|
3450
|
-
const contractPath = path.join(
|
|
3619
|
+
async function resolveContractFilePath(projectRoot, input) {
|
|
3620
|
+
const materializedPath = path.join(
|
|
3451
3621
|
projectRoot,
|
|
3452
3622
|
"agent-kit",
|
|
3453
3623
|
"materialized-contracts",
|
|
3454
|
-
pluginName,
|
|
3455
|
-
`${contractName}.jay-contract`
|
|
3624
|
+
input.pluginName,
|
|
3625
|
+
`${input.contractName}.jay-contract`
|
|
3456
3626
|
);
|
|
3457
|
-
|
|
3627
|
+
const indexPath = path.join(projectRoot, "agent-kit", "plugins-index.yaml");
|
|
3628
|
+
try {
|
|
3629
|
+
const indexYaml = await fs.readFile(indexPath, "utf-8");
|
|
3630
|
+
const index = load(indexYaml);
|
|
3631
|
+
const plugin = index.plugins?.find((p) => p.name === input.pluginName);
|
|
3632
|
+
const contract = plugin?.contracts?.find(
|
|
3633
|
+
(c) => c.name === input.contractName
|
|
3634
|
+
);
|
|
3635
|
+
if (contract?.path) {
|
|
3636
|
+
const resolved = path.resolve(
|
|
3637
|
+
projectRoot,
|
|
3638
|
+
contract.path.replace(/^\.\//, "")
|
|
3639
|
+
);
|
|
3640
|
+
try {
|
|
3641
|
+
await fs.access(resolved);
|
|
3642
|
+
return resolved;
|
|
3643
|
+
} catch {
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
} catch (e) {
|
|
3647
|
+
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
3648
|
+
if (code !== "ENOENT") throw e;
|
|
3649
|
+
}
|
|
3650
|
+
try {
|
|
3651
|
+
await fs.access(materializedPath);
|
|
3652
|
+
return materializedPath;
|
|
3653
|
+
} catch {
|
|
3654
|
+
throw new Error(
|
|
3655
|
+
`Contract "${input.contractName}" not found for plugin "${input.pluginName}". Check agent-kit/plugins-index.yaml or run \`yarn agent-kit\`.`
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
async function resolveContractParamsForItem(projectRoot, item) {
|
|
3660
|
+
const pluginName = item.pluginName ?? pluginNameFromItemId(item.id);
|
|
3661
|
+
const contractName = contractNameFromItemId(item.id);
|
|
3662
|
+
if (!pluginName || !contractName) return null;
|
|
3663
|
+
let yaml2;
|
|
3458
3664
|
try {
|
|
3459
|
-
|
|
3665
|
+
const contractPath = await resolveContractFilePath(projectRoot, {
|
|
3666
|
+
pluginName,
|
|
3667
|
+
contractName
|
|
3668
|
+
});
|
|
3669
|
+
yaml2 = await fs.readFile(contractPath, "utf-8");
|
|
3460
3670
|
} catch {
|
|
3461
3671
|
return null;
|
|
3462
3672
|
}
|
|
3463
|
-
return parseContractParamsFromYaml(
|
|
3673
|
+
return parseContractParamsFromYaml(yaml2, contractName, pluginName);
|
|
3464
3674
|
}
|
|
3465
3675
|
const listAddMenuItemsAction = makeJayQuery("aiditor.listAddMenuItems").withServices(DEV_SERVER_SERVICE).withHandler(async () => {
|
|
3466
3676
|
const projectRoot = process.cwd();
|
|
@@ -3792,6 +4002,460 @@ const writePluginSourceAction = makeJayQuery("aiditor.writePluginSource").withSe
|
|
|
3792
4002
|
await writePluginSource(projectRoot, input);
|
|
3793
4003
|
return { ok: true };
|
|
3794
4004
|
});
|
|
4005
|
+
const PAGE_ASSETS_VERSION = 1;
|
|
4006
|
+
function pageAssetsDirSegment(contextKey) {
|
|
4007
|
+
const trimmed = contextKey.replace(/^\//, "").replace(/\/$/, "");
|
|
4008
|
+
return trimmed === "" ? "_root" : trimmed.replace(/\//g, "--");
|
|
4009
|
+
}
|
|
4010
|
+
function emptyPageAssetsFile(input) {
|
|
4011
|
+
return {
|
|
4012
|
+
version: PAGE_ASSETS_VERSION,
|
|
4013
|
+
contextKey: input.contextKey,
|
|
4014
|
+
pageRoute: input.pageRoute,
|
|
4015
|
+
renderedUrl: input.renderedUrl,
|
|
4016
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4017
|
+
assets: [],
|
|
4018
|
+
suppressedDetectedKeys: []
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
function getPageAssetsFilePath(projectDir, contextKey) {
|
|
4022
|
+
const segment = pageAssetsDirSegment(contextKey);
|
|
4023
|
+
return path.join(
|
|
4024
|
+
projectDir,
|
|
4025
|
+
".aiditor",
|
|
4026
|
+
"page-assets",
|
|
4027
|
+
segment,
|
|
4028
|
+
"page-assets.json"
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
const fileMutexChains$1 = /* @__PURE__ */ new Map();
|
|
4032
|
+
async function withPageAssetsFileLock(filePath, fn) {
|
|
4033
|
+
const previous = fileMutexChains$1.get(filePath) ?? Promise.resolve();
|
|
4034
|
+
let release;
|
|
4035
|
+
const next = new Promise((resolve) => {
|
|
4036
|
+
release = resolve;
|
|
4037
|
+
});
|
|
4038
|
+
fileMutexChains$1.set(filePath, next);
|
|
4039
|
+
await previous;
|
|
4040
|
+
try {
|
|
4041
|
+
return await fn();
|
|
4042
|
+
} finally {
|
|
4043
|
+
release();
|
|
4044
|
+
if (fileMutexChains$1.get(filePath) === next) {
|
|
4045
|
+
fileMutexChains$1.delete(filePath);
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
async function readPageAssetsFile(filePath, fallback) {
|
|
4050
|
+
try {
|
|
4051
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
4052
|
+
const parsed = JSON.parse(raw);
|
|
4053
|
+
if (parsed.version !== PAGE_ASSETS_VERSION || typeof parsed.contextKey !== "string" || typeof parsed.pageRoute !== "string" || !Array.isArray(parsed.assets)) {
|
|
4054
|
+
return fallback;
|
|
4055
|
+
}
|
|
4056
|
+
return {
|
|
4057
|
+
version: PAGE_ASSETS_VERSION,
|
|
4058
|
+
contextKey: parsed.contextKey,
|
|
4059
|
+
pageRoute: parsed.pageRoute,
|
|
4060
|
+
renderedUrl: typeof parsed.renderedUrl === "string" ? parsed.renderedUrl : void 0,
|
|
4061
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
4062
|
+
assets: parsed.assets.filter(
|
|
4063
|
+
(a) => !!a && typeof a === "object" && typeof a.id === "string" && typeof a.title === "string" && typeof a.prompt === "string"
|
|
4064
|
+
),
|
|
4065
|
+
suppressedDetectedKeys: Array.isArray(parsed.suppressedDetectedKeys) ? parsed.suppressedDetectedKeys.filter(
|
|
4066
|
+
(k) => typeof k === "string"
|
|
4067
|
+
) : [],
|
|
4068
|
+
routeMigration: parsed.routeMigration
|
|
4069
|
+
};
|
|
4070
|
+
} catch (e) {
|
|
4071
|
+
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
4072
|
+
if (code === "ENOENT") {
|
|
4073
|
+
return fallback;
|
|
4074
|
+
}
|
|
4075
|
+
throw e;
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
async function writePageAssetsFileAtomic(filePath, file) {
|
|
4079
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
4080
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
4081
|
+
const body = `${JSON.stringify(file, null, 2)}
|
|
4082
|
+
`;
|
|
4083
|
+
await fs.writeFile(tmp, body, "utf-8");
|
|
4084
|
+
await fs.rename(tmp, filePath);
|
|
4085
|
+
}
|
|
4086
|
+
async function getPageAssets(projectDir, input) {
|
|
4087
|
+
const filePath = getPageAssetsFilePath(projectDir, input.contextKey);
|
|
4088
|
+
const fallback = emptyPageAssetsFile(input);
|
|
4089
|
+
return withPageAssetsFileLock(
|
|
4090
|
+
filePath,
|
|
4091
|
+
async () => readPageAssetsFile(filePath, fallback)
|
|
4092
|
+
);
|
|
4093
|
+
}
|
|
4094
|
+
async function savePageAssets(projectDir, file) {
|
|
4095
|
+
const filePath = getPageAssetsFilePath(projectDir, file.contextKey);
|
|
4096
|
+
const next = {
|
|
4097
|
+
...file,
|
|
4098
|
+
version: PAGE_ASSETS_VERSION,
|
|
4099
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4100
|
+
};
|
|
4101
|
+
await withPageAssetsFileLock(filePath, async () => {
|
|
4102
|
+
await writePageAssetsFileAtomic(filePath, next);
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
const PAGE_META_VERSION = 1;
|
|
4106
|
+
function pageMetaDirSegment(contextKey) {
|
|
4107
|
+
const trimmed = contextKey.replace(/^\//, "").replace(/\/$/, "");
|
|
4108
|
+
return trimmed === "" ? "_root" : trimmed.replace(/\//g, "--");
|
|
4109
|
+
}
|
|
4110
|
+
function emptyPageMetaFile(input) {
|
|
4111
|
+
return {
|
|
4112
|
+
version: PAGE_META_VERSION,
|
|
4113
|
+
contextKey: input.contextKey,
|
|
4114
|
+
pageRoute: input.pageRoute,
|
|
4115
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
function getPageMetaFilePath(projectDir, contextKey) {
|
|
4119
|
+
const segment = pageMetaDirSegment(contextKey);
|
|
4120
|
+
return path.join(
|
|
4121
|
+
projectDir,
|
|
4122
|
+
".aiditor",
|
|
4123
|
+
"page-meta",
|
|
4124
|
+
segment,
|
|
4125
|
+
"page-meta.json"
|
|
4126
|
+
);
|
|
4127
|
+
}
|
|
4128
|
+
const fileMutexChains = /* @__PURE__ */ new Map();
|
|
4129
|
+
async function withPageMetaFileLock(filePath, fn) {
|
|
4130
|
+
const previous = fileMutexChains.get(filePath) ?? Promise.resolve();
|
|
4131
|
+
let release;
|
|
4132
|
+
const next = new Promise((resolve) => {
|
|
4133
|
+
release = resolve;
|
|
4134
|
+
});
|
|
4135
|
+
fileMutexChains.set(filePath, next);
|
|
4136
|
+
await previous;
|
|
4137
|
+
try {
|
|
4138
|
+
return await fn();
|
|
4139
|
+
} finally {
|
|
4140
|
+
release();
|
|
4141
|
+
if (fileMutexChains.get(filePath) === next) {
|
|
4142
|
+
fileMutexChains.delete(filePath);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
async function readPageMetaFile(filePath, fallback) {
|
|
4147
|
+
try {
|
|
4148
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
4149
|
+
const parsed = JSON.parse(raw);
|
|
4150
|
+
if (parsed.version !== PAGE_META_VERSION || typeof parsed.contextKey !== "string" || typeof parsed.pageRoute !== "string") {
|
|
4151
|
+
return fallback;
|
|
4152
|
+
}
|
|
4153
|
+
return {
|
|
4154
|
+
version: PAGE_META_VERSION,
|
|
4155
|
+
contextKey: parsed.contextKey,
|
|
4156
|
+
pageRoute: parsed.pageRoute,
|
|
4157
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
4158
|
+
plannedRoute: typeof parsed.plannedRoute === "string" ? parsed.plannedRoute : void 0,
|
|
4159
|
+
routeMigration: parsed.routeMigration
|
|
4160
|
+
};
|
|
4161
|
+
} catch (e) {
|
|
4162
|
+
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
4163
|
+
if (code === "ENOENT") {
|
|
4164
|
+
return fallback;
|
|
4165
|
+
}
|
|
4166
|
+
throw e;
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
async function readLegacyPageAssets(projectDir, contextKey) {
|
|
4170
|
+
const filePath = getPageAssetsFilePath(projectDir, contextKey);
|
|
4171
|
+
try {
|
|
4172
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
4173
|
+
const parsed = JSON.parse(raw);
|
|
4174
|
+
if (typeof parsed.contextKey !== "string") return null;
|
|
4175
|
+
return parsed;
|
|
4176
|
+
} catch {
|
|
4177
|
+
return null;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
async function writePageMetaFileAtomic(filePath, file) {
|
|
4181
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
4182
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
4183
|
+
const body = `${JSON.stringify(file, null, 2)}
|
|
4184
|
+
`;
|
|
4185
|
+
await fs.writeFile(tmp, body, "utf-8");
|
|
4186
|
+
await fs.rename(tmp, filePath);
|
|
4187
|
+
}
|
|
4188
|
+
async function fileExists(filePath) {
|
|
4189
|
+
try {
|
|
4190
|
+
await fs.access(filePath);
|
|
4191
|
+
return true;
|
|
4192
|
+
} catch {
|
|
4193
|
+
return false;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
async function getPageMeta(projectDir, input) {
|
|
4197
|
+
const filePath = getPageMetaFilePath(projectDir, input.contextKey);
|
|
4198
|
+
const fallback = emptyPageMetaFile(input);
|
|
4199
|
+
return withPageMetaFileLock(filePath, async () => {
|
|
4200
|
+
if (await fileExists(filePath)) {
|
|
4201
|
+
return readPageMetaFile(filePath, fallback);
|
|
4202
|
+
}
|
|
4203
|
+
const legacy = await readLegacyPageAssets(projectDir, input.contextKey);
|
|
4204
|
+
if (!legacy) return fallback;
|
|
4205
|
+
const migrated = {
|
|
4206
|
+
...fallback,
|
|
4207
|
+
contextKey: input.contextKey,
|
|
4208
|
+
pageRoute: input.pageRoute,
|
|
4209
|
+
plannedRoute: legacy.routeMigration?.plannedRoute,
|
|
4210
|
+
routeMigration: legacy.routeMigration,
|
|
4211
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4212
|
+
};
|
|
4213
|
+
await writePageMetaFileAtomic(filePath, migrated);
|
|
4214
|
+
return migrated;
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
async function savePageMeta(projectDir, file) {
|
|
4218
|
+
const filePath = getPageMetaFilePath(projectDir, file.contextKey);
|
|
4219
|
+
const next = {
|
|
4220
|
+
...file,
|
|
4221
|
+
version: PAGE_META_VERSION,
|
|
4222
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4223
|
+
};
|
|
4224
|
+
await withPageMetaFileLock(filePath, async () => {
|
|
4225
|
+
await writePageMetaFileAtomic(filePath, next);
|
|
4226
|
+
});
|
|
4227
|
+
}
|
|
4228
|
+
function formatTagDescription(description) {
|
|
4229
|
+
if (!description?.length) return void 0;
|
|
4230
|
+
return description.join(" ");
|
|
4231
|
+
}
|
|
4232
|
+
function tagKindFromTypes(types) {
|
|
4233
|
+
if (types.includes(ContractTagType.subContract)) return "sub-contract";
|
|
4234
|
+
if (types.includes(ContractTagType.interactive)) return "interactive";
|
|
4235
|
+
if (types.includes(ContractTagType.variant)) return "variant";
|
|
4236
|
+
return "data";
|
|
4237
|
+
}
|
|
4238
|
+
function flattenTags(tags, parentPath = "") {
|
|
4239
|
+
const results = [];
|
|
4240
|
+
for (const tag of tags) {
|
|
4241
|
+
const tagPath = parentPath ? `${parentPath}.${tag.tag}` : tag.tag;
|
|
4242
|
+
results.push({
|
|
4243
|
+
tagPath,
|
|
4244
|
+
tagKind: tagKindFromTypes(tag.type),
|
|
4245
|
+
linkedContract: tag.link,
|
|
4246
|
+
description: formatTagDescription(tag.description)
|
|
4247
|
+
});
|
|
4248
|
+
if (tag.tags?.length) {
|
|
4249
|
+
for (const child of tag.tags) {
|
|
4250
|
+
const childPath = `${tagPath}.${child.tag}`;
|
|
4251
|
+
results.push({
|
|
4252
|
+
tagPath: childPath,
|
|
4253
|
+
tagKind: tagKindFromTypes(child.type),
|
|
4254
|
+
linkedContract: child.link,
|
|
4255
|
+
description: formatTagDescription(child.description)
|
|
4256
|
+
});
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
return results;
|
|
4261
|
+
}
|
|
4262
|
+
async function resolveContractTagsForComponent(projectRoot, input) {
|
|
4263
|
+
const contractPath = await resolveContractFilePath(projectRoot, input);
|
|
4264
|
+
const yaml2 = await fs.readFile(contractPath, "utf-8");
|
|
4265
|
+
const fileName = path.basename(contractPath);
|
|
4266
|
+
const contract = checkValidationErrors(
|
|
4267
|
+
parseContract(yaml2, fileName)
|
|
4268
|
+
);
|
|
4269
|
+
return flattenTags(contract.tags);
|
|
4270
|
+
}
|
|
4271
|
+
const getPageMetaAction = makeJayQuery("aiditor.getPageMeta").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
|
|
4272
|
+
const projectRoot = process.cwd();
|
|
4273
|
+
const file = await getPageMeta(projectRoot, input);
|
|
4274
|
+
return { file };
|
|
4275
|
+
});
|
|
4276
|
+
const savePageMetaAction = makeJayQuery("aiditor.savePageMeta").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
|
|
4277
|
+
const projectRoot = process.cwd();
|
|
4278
|
+
await savePageMeta(projectRoot, input.file);
|
|
4279
|
+
return { ok: true };
|
|
4280
|
+
});
|
|
4281
|
+
const getContractInspectorTagsAction = makeJayQuery(
|
|
4282
|
+
"aiditor.getContractInspectorTags"
|
|
4283
|
+
).withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
|
|
4284
|
+
const projectRoot = process.cwd();
|
|
4285
|
+
const tags = await resolveContractTagsForComponent(projectRoot, input);
|
|
4286
|
+
return { tags };
|
|
4287
|
+
});
|
|
4288
|
+
function pluginNameFromPackageAttr(pluginAttr) {
|
|
4289
|
+
const prefix = "@jay-framework/";
|
|
4290
|
+
if (pluginAttr.startsWith(prefix)) {
|
|
4291
|
+
return pluginAttr.slice(prefix.length);
|
|
4292
|
+
}
|
|
4293
|
+
return pluginAttr;
|
|
4294
|
+
}
|
|
4295
|
+
const HEADLESS_SCRIPT_RE = /<script[^>]*type\s*=\s*["']application\/jay-headless["'][^>]*>/gi;
|
|
4296
|
+
const JAY_TAG_RE = /<jay:([a-zA-Z][\w-]*)\b/g;
|
|
4297
|
+
function readScriptAttr(tag, attr) {
|
|
4298
|
+
const re = new RegExp(`${attr}\\s*=\\s*["']([^"']+)["']`, "i");
|
|
4299
|
+
const m = tag.match(re);
|
|
4300
|
+
return m?.[1]?.trim();
|
|
4301
|
+
}
|
|
4302
|
+
function humanizeContractName(contract) {
|
|
4303
|
+
return contract.split(/[-_]/u).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
4304
|
+
}
|
|
4305
|
+
function catalogItemForDetection(pluginName, contract, catalogByPluginContract) {
|
|
4306
|
+
return catalogByPluginContract.get(`${pluginName}:${contract}`);
|
|
4307
|
+
}
|
|
4308
|
+
function buildCatalogLookup(items) {
|
|
4309
|
+
const map = /* @__PURE__ */ new Map();
|
|
4310
|
+
for (const item of items) {
|
|
4311
|
+
if (item.pluginName && item.id.includes(":")) {
|
|
4312
|
+
const contract = item.id.split(":").slice(1).join(":");
|
|
4313
|
+
map.set(`${item.pluginName}:${contract}`, item);
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
return map;
|
|
4317
|
+
}
|
|
4318
|
+
function detectPageComponentsFromHtml(pageHtml, catalogItems = []) {
|
|
4319
|
+
const catalogByPluginContract = buildCatalogLookup(catalogItems);
|
|
4320
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4321
|
+
const results = [];
|
|
4322
|
+
const headlessTags = pageHtml.match(HEADLESS_SCRIPT_RE) ?? [];
|
|
4323
|
+
for (const tag of headlessTags) {
|
|
4324
|
+
const pluginAttr = readScriptAttr(tag, "plugin");
|
|
4325
|
+
const contract = readScriptAttr(tag, "contract");
|
|
4326
|
+
const key = readScriptAttr(tag, "key");
|
|
4327
|
+
if (!pluginAttr || !contract) continue;
|
|
4328
|
+
const pluginName = pluginNameFromPackageAttr(pluginAttr);
|
|
4329
|
+
const detectedKey = key ? `headless:${pluginName}:${contract}:${key}` : `headless:${pluginName}:${contract}`;
|
|
4330
|
+
if (seen.has(detectedKey)) continue;
|
|
4331
|
+
seen.add(detectedKey);
|
|
4332
|
+
const catalogItem = catalogItemForDetection(
|
|
4333
|
+
pluginName,
|
|
4334
|
+
contract,
|
|
4335
|
+
catalogByPluginContract
|
|
4336
|
+
);
|
|
4337
|
+
results.push({
|
|
4338
|
+
detectedKey,
|
|
4339
|
+
title: catalogItem?.title ?? humanizeContractName(contract),
|
|
4340
|
+
prompt: catalogItem?.prompt ?? `Use headless component @jay-framework/${pluginName} / contract ${contract}.${key ? ` Script key: ${key}.` : ""}`,
|
|
4341
|
+
pluginName,
|
|
4342
|
+
contractName: contract,
|
|
4343
|
+
componentKey: key
|
|
4344
|
+
});
|
|
4345
|
+
}
|
|
4346
|
+
let jayMatch;
|
|
4347
|
+
const jayRe = new RegExp(JAY_TAG_RE.source, "g");
|
|
4348
|
+
while ((jayMatch = jayRe.exec(pageHtml)) !== null) {
|
|
4349
|
+
const rawTag = jayMatch[1];
|
|
4350
|
+
if (!rawTag) continue;
|
|
4351
|
+
const tagName = rawTag.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
4352
|
+
const detectedKey = `jay:${tagName}`;
|
|
4353
|
+
if (seen.has(detectedKey)) continue;
|
|
4354
|
+
seen.add(detectedKey);
|
|
4355
|
+
const contractGuess = tagName;
|
|
4356
|
+
const catalogItem = [...catalogItems].find((item) => {
|
|
4357
|
+
const idSuffix = item.id.split(":").slice(1).join(":");
|
|
4358
|
+
return idSuffix === contractGuess || idSuffix === tagName;
|
|
4359
|
+
});
|
|
4360
|
+
results.push({
|
|
4361
|
+
detectedKey,
|
|
4362
|
+
title: catalogItem?.title ?? humanizeContractName(tagName),
|
|
4363
|
+
prompt: catalogItem?.prompt ?? `Use <jay:${rawTag}> component on this page per agent-kit designer docs.`,
|
|
4364
|
+
pluginName: catalogItem?.pluginName,
|
|
4365
|
+
contractName: contractGuess
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4368
|
+
return results;
|
|
4369
|
+
}
|
|
4370
|
+
function detectedToPageAssetEntry(detected) {
|
|
4371
|
+
return {
|
|
4372
|
+
id: crypto.randomUUID(),
|
|
4373
|
+
title: detected.title,
|
|
4374
|
+
prompt: detected.prompt,
|
|
4375
|
+
source: "detected",
|
|
4376
|
+
itemId: detected.pluginName ? `${detected.pluginName}:${detected.contractName ?? ""}` : void 0,
|
|
4377
|
+
persistOnPage: true,
|
|
4378
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4379
|
+
detectedKey: detected.detectedKey,
|
|
4380
|
+
detectedMeta: {
|
|
4381
|
+
contractName: detected.contractName,
|
|
4382
|
+
pluginName: detected.pluginName,
|
|
4383
|
+
componentKey: detected.componentKey
|
|
4384
|
+
}
|
|
4385
|
+
};
|
|
4386
|
+
}
|
|
4387
|
+
function mergeDetectedPageAssets(existingAssets, detected, suppressedDetectedKeys = []) {
|
|
4388
|
+
const suppressed = new Set(suppressedDetectedKeys);
|
|
4389
|
+
const activeDetectedKeys = new Set(
|
|
4390
|
+
existingAssets.filter((a) => a.source === "detected" && !a.userRemoved && a.detectedKey).map((a) => a.detectedKey)
|
|
4391
|
+
);
|
|
4392
|
+
const detectedKeySet = new Set(detected.map((d) => d.detectedKey));
|
|
4393
|
+
const nextAssets = existingAssets.filter((asset) => {
|
|
4394
|
+
if (asset.source !== "detected" || asset.userRemoved) return true;
|
|
4395
|
+
if (!asset.detectedKey) return true;
|
|
4396
|
+
return detectedKeySet.has(asset.detectedKey);
|
|
4397
|
+
});
|
|
4398
|
+
for (const component of detected) {
|
|
4399
|
+
if (suppressed.has(component.detectedKey)) continue;
|
|
4400
|
+
const tombstoned = nextAssets.some(
|
|
4401
|
+
(a) => a.source === "detected" && a.detectedKey === component.detectedKey && a.userRemoved
|
|
4402
|
+
);
|
|
4403
|
+
if (tombstoned) continue;
|
|
4404
|
+
if (activeDetectedKeys.has(component.detectedKey)) continue;
|
|
4405
|
+
nextAssets.push(detectedToPageAssetEntry(component));
|
|
4406
|
+
activeDetectedKeys.add(component.detectedKey);
|
|
4407
|
+
}
|
|
4408
|
+
return {
|
|
4409
|
+
assets: nextAssets,
|
|
4410
|
+
suppressedDetectedKeys: [...suppressed]
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
const getPageAssetsAction = makeJayQuery("aiditor.getPageAssets").withServices(DEV_SERVER_SERVICE).withHandler(
|
|
4414
|
+
async (input) => {
|
|
4415
|
+
const projectRoot = process.cwd();
|
|
4416
|
+
const file = await getPageAssets(projectRoot, input);
|
|
4417
|
+
return { file };
|
|
4418
|
+
}
|
|
4419
|
+
);
|
|
4420
|
+
const savePageAssetsAction = makeJayQuery("aiditor.savePageAssets").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
|
|
4421
|
+
const projectRoot = process.cwd();
|
|
4422
|
+
await savePageAssets(projectRoot, input.file);
|
|
4423
|
+
return { ok: true };
|
|
4424
|
+
});
|
|
4425
|
+
const syncDetectedPageAssetsAction = makeJayQuery(
|
|
4426
|
+
"aiditor.syncDetectedPageAssets"
|
|
4427
|
+
).withServices(DEV_SERVER_SERVICE).withHandler(
|
|
4428
|
+
async (input) => {
|
|
4429
|
+
const projectRoot = process.cwd();
|
|
4430
|
+
const file = await getPageAssets(projectRoot, input);
|
|
4431
|
+
let pageHtml = "";
|
|
4432
|
+
const jayHtmlPath = input.jayHtmlPath ?? await resolveJayHtmlPathForPageRoute(projectRoot, input.pageRoute);
|
|
4433
|
+
if (jayHtmlPath) {
|
|
4434
|
+
try {
|
|
4435
|
+
pageHtml = await fs.readFile(jayHtmlPath, "utf-8");
|
|
4436
|
+
} catch {
|
|
4437
|
+
pageHtml = "";
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
const catalog = await listAddMenuItems(projectRoot);
|
|
4441
|
+
const detected = detectPageComponentsFromHtml(pageHtml, catalog.items);
|
|
4442
|
+
const merged = mergeDetectedPageAssets(
|
|
4443
|
+
file.assets,
|
|
4444
|
+
detected,
|
|
4445
|
+
file.suppressedDetectedKeys ?? []
|
|
4446
|
+
);
|
|
4447
|
+
const next = {
|
|
4448
|
+
...file,
|
|
4449
|
+
contextKey: input.contextKey,
|
|
4450
|
+
pageRoute: input.pageRoute,
|
|
4451
|
+
renderedUrl: input.renderedUrl ?? file.renderedUrl,
|
|
4452
|
+
assets: merged.assets,
|
|
4453
|
+
suppressedDetectedKeys: merged.suppressedDetectedKeys
|
|
4454
|
+
};
|
|
4455
|
+
await savePageAssets(projectRoot, next);
|
|
4456
|
+
return { file: next, detectedCount: detected.length };
|
|
4457
|
+
}
|
|
4458
|
+
);
|
|
3795
4459
|
const AIDITOR_PUBLISH_SCRIPT = "aiditor:publish";
|
|
3796
4460
|
async function projectHasAiditorPublishScript(projectRoot) {
|
|
3797
4461
|
try {
|
|
@@ -3956,6 +4620,9 @@ export {
|
|
|
3956
4620
|
ensureProjectPluginAction,
|
|
3957
4621
|
generateAddPageBriefFromImageAction,
|
|
3958
4622
|
getAiditorBootstrap,
|
|
4623
|
+
getContractInspectorTagsAction,
|
|
4624
|
+
getPageAssetsAction,
|
|
4625
|
+
getPageMetaAction,
|
|
3959
4626
|
getPageParamsAction,
|
|
3960
4627
|
getPluginSetupStatusAction,
|
|
3961
4628
|
getProjectInfoAction,
|
|
@@ -3970,12 +4637,15 @@ export {
|
|
|
3970
4637
|
resolveAddMenuContractParamsAction,
|
|
3971
4638
|
runAiditorPublishAction,
|
|
3972
4639
|
saveAddPageDraftAction,
|
|
4640
|
+
savePageAssetsAction,
|
|
4641
|
+
savePageMetaAction,
|
|
3973
4642
|
setupAiditor,
|
|
3974
4643
|
startAddPageRequestAction,
|
|
3975
4644
|
submitAddPageAction,
|
|
3976
4645
|
submitTaskAction,
|
|
3977
4646
|
syncAddMenuAttachmentsAction,
|
|
3978
4647
|
syncAddPagePluginManifestAction,
|
|
4648
|
+
syncDetectedPageAssetsAction,
|
|
3979
4649
|
uploadAddPageAssetAction,
|
|
3980
4650
|
writePluginSourceAction
|
|
3981
4651
|
};
|