@jay-framework/aiditor 0.19.6 → 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/cancel-agent-task.jay-action +9 -0
- package/dist/actions/cancel-agent-task.jay-action.d.ts +8 -0
- package/dist/actions/check-aiditor-publish.jay-action +7 -0
- package/dist/actions/check-aiditor-publish.jay-action.d.ts +5 -0
- 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/run-aiditor-publish.jay-action +7 -0
- package/dist/actions/run-aiditor-publish.jay-action.d.ts +5 -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 +451 -210
- package/dist/index.client.js +9364 -1552
- package/dist/index.d.ts +176 -4
- package/dist/index.js +974 -86
- package/dist/pages/aiditor/page.css +2272 -594
- package/dist/pages/aiditor/page.jay-html +4310 -1374
- package/dist/pages/aiditor/page.jay-html.d.ts +1013 -0
- package/package.json +20 -7
- package/plugin.yaml +18 -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) {
|
|
@@ -230,6 +232,49 @@ async function replaceRouteSessionAfterStaleResume(projectDir, routeKey) {
|
|
|
230
232
|
return { sessionId };
|
|
231
233
|
});
|
|
232
234
|
}
|
|
235
|
+
function isDynamicJayRoute(templateRoute) {
|
|
236
|
+
return templateRoute.includes(":") || templateRoute.includes("[");
|
|
237
|
+
}
|
|
238
|
+
function resolveAgentContextKey(input) {
|
|
239
|
+
const template = normalizePageRoute(input.pageRoute);
|
|
240
|
+
if (!isDynamicJayRoute(template)) {
|
|
241
|
+
return template;
|
|
242
|
+
}
|
|
243
|
+
if (input.renderedUrl?.trim()) {
|
|
244
|
+
try {
|
|
245
|
+
const pathname = new URL(input.renderedUrl.trim(), "http://aiditor.local").pathname;
|
|
246
|
+
return normalizePageRoute(pathname);
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return template;
|
|
251
|
+
}
|
|
252
|
+
const activeTokens = /* @__PURE__ */ new Map();
|
|
253
|
+
const cancelledRoutes = /* @__PURE__ */ new Set();
|
|
254
|
+
function beginCancellableAgentTask(routeKey) {
|
|
255
|
+
const token = Symbol("agent-task-cancel");
|
|
256
|
+
activeTokens.set(routeKey, token);
|
|
257
|
+
cancelledRoutes.delete(routeKey);
|
|
258
|
+
return token;
|
|
259
|
+
}
|
|
260
|
+
function endCancellableAgentTask(routeKey, token) {
|
|
261
|
+
if (activeTokens.get(routeKey) !== token) return;
|
|
262
|
+
activeTokens.delete(routeKey);
|
|
263
|
+
cancelledRoutes.delete(routeKey);
|
|
264
|
+
}
|
|
265
|
+
function isAgentTaskCancelled(routeKey) {
|
|
266
|
+
return cancelledRoutes.has(routeKey);
|
|
267
|
+
}
|
|
268
|
+
function cancelAgentTask(routeKey) {
|
|
269
|
+
const hadActiveTask = activeTokens.has(routeKey);
|
|
270
|
+
cancelledRoutes.add(routeKey);
|
|
271
|
+
endRouteQuery(routeKey);
|
|
272
|
+
return hadActiveTask;
|
|
273
|
+
}
|
|
274
|
+
function forceReleaseAgentRoute(routeKey) {
|
|
275
|
+
cancelledRoutes.add(routeKey);
|
|
276
|
+
endRouteQuery(routeKey);
|
|
277
|
+
}
|
|
233
278
|
const AGENT_KIT_PREAMBLE = `You are working in a Jay Stack project. The current working directory is the project root.
|
|
234
279
|
- Read agent-kit/plugins-index.yaml for installed plugins and materialized contract paths.
|
|
235
280
|
- Follow Jay Stack conventions for pages, routing, and headless component binding.
|
|
@@ -272,6 +317,31 @@ function buildChatPrompt(config, pageRoute, renderedUrl, userMessage) {
|
|
|
272
317
|
];
|
|
273
318
|
return lines.filter((l) => l !== null).join("\n");
|
|
274
319
|
}
|
|
320
|
+
function groupAddMenuItems(items) {
|
|
321
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const item of items) {
|
|
323
|
+
const sub = item.subCategory ?? null;
|
|
324
|
+
let subMap = byCategory.get(item.category);
|
|
325
|
+
if (!subMap) {
|
|
326
|
+
subMap = /* @__PURE__ */ new Map();
|
|
327
|
+
byCategory.set(item.category, subMap);
|
|
328
|
+
}
|
|
329
|
+
const list = subMap.get(sub) ?? [];
|
|
330
|
+
list.push(item);
|
|
331
|
+
subMap.set(sub, list);
|
|
332
|
+
}
|
|
333
|
+
return [...byCategory.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, subMap]) => ({
|
|
334
|
+
category,
|
|
335
|
+
subCategories: [...subMap.entries()].sort(([a], [b]) => {
|
|
336
|
+
const sa = a ?? "";
|
|
337
|
+
const sb = b ?? "";
|
|
338
|
+
return sa.localeCompare(sb);
|
|
339
|
+
}).map(([subCategory, subItems]) => ({
|
|
340
|
+
subCategory,
|
|
341
|
+
items: subItems.sort((a, b) => a.title.localeCompare(b.title))
|
|
342
|
+
}))
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
275
345
|
const REJECTED_ITEM_FIELDS = [
|
|
276
346
|
"kind",
|
|
277
347
|
"parameters",
|
|
@@ -299,6 +369,30 @@ function optionalString(obj, field) {
|
|
|
299
369
|
const trimmed = value.trim();
|
|
300
370
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
301
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
|
+
}
|
|
302
396
|
function validateAddMenuItem(raw, path2) {
|
|
303
397
|
const errors = [];
|
|
304
398
|
if (!isRecord(raw)) {
|
|
@@ -322,6 +416,11 @@ function validateAddMenuItem(raw, path2) {
|
|
|
322
416
|
if (errors.length > 0 || !id || !title || !category || !prompt) {
|
|
323
417
|
return { item: null, errors };
|
|
324
418
|
}
|
|
419
|
+
const interaction = validateInteraction(
|
|
420
|
+
raw.interaction,
|
|
421
|
+
`${path2}.interaction`,
|
|
422
|
+
errors
|
|
423
|
+
);
|
|
325
424
|
return {
|
|
326
425
|
item: {
|
|
327
426
|
id,
|
|
@@ -331,7 +430,8 @@ function validateAddMenuItem(raw, path2) {
|
|
|
331
430
|
pluginName: optionalString(raw, "pluginName"),
|
|
332
431
|
packageName: optionalString(raw, "packageName"),
|
|
333
432
|
subCategory: optionalString(raw, "subCategory"),
|
|
334
|
-
thumbnail: optionalString(raw, "thumbnail")
|
|
433
|
+
thumbnail: optionalString(raw, "thumbnail"),
|
|
434
|
+
...interaction ? { interaction } : {}
|
|
335
435
|
},
|
|
336
436
|
errors
|
|
337
437
|
};
|
|
@@ -363,11 +463,11 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
|
363
463
|
}
|
|
364
464
|
return { file: { items }, errors };
|
|
365
465
|
}
|
|
366
|
-
function parsePluginsIndexYaml(
|
|
466
|
+
function parsePluginsIndexYaml(yaml2) {
|
|
367
467
|
const entries = [];
|
|
368
468
|
let currentPlugin = null;
|
|
369
469
|
let inContracts = false;
|
|
370
|
-
for (const line of
|
|
470
|
+
for (const line of yaml2.split("\n")) {
|
|
371
471
|
const pluginMatch = line.match(/^ - name:\s*(.+)$/);
|
|
372
472
|
if (pluginMatch && !line.startsWith(" ")) {
|
|
373
473
|
currentPlugin = pluginMatch[1].trim();
|
|
@@ -444,8 +544,8 @@ async function isPluginInstalled(projectRoot, pluginName) {
|
|
|
444
544
|
async function parsePluginsIndexContracts(projectRoot) {
|
|
445
545
|
const indexPath = path.join(projectRoot, "agent-kit", "plugins-index.yaml");
|
|
446
546
|
try {
|
|
447
|
-
const
|
|
448
|
-
return parsePluginsIndexYaml(
|
|
547
|
+
const yaml2 = await fs.readFile(indexPath, "utf-8");
|
|
548
|
+
return parsePluginsIndexYaml(yaml2);
|
|
449
549
|
} catch (e) {
|
|
450
550
|
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
451
551
|
if (code === "ENOENT") return [];
|
|
@@ -461,10 +561,10 @@ async function parsePluginYamlContracts(projectRoot, pluginName) {
|
|
|
461
561
|
"plugin.yaml"
|
|
462
562
|
);
|
|
463
563
|
try {
|
|
464
|
-
const
|
|
564
|
+
const yaml2 = await fs.readFile(pluginYamlPath, "utf-8");
|
|
465
565
|
const entries = [];
|
|
466
566
|
let inContracts = false;
|
|
467
|
-
for (const line of
|
|
567
|
+
for (const line of yaml2.split("\n")) {
|
|
468
568
|
if (line.trim() === "contracts:") {
|
|
469
569
|
inContracts = true;
|
|
470
570
|
continue;
|
|
@@ -503,31 +603,6 @@ function pluginNameFromItem(item) {
|
|
|
503
603
|
if (colon > 0) return item.id.slice(0, colon);
|
|
504
604
|
return null;
|
|
505
605
|
}
|
|
506
|
-
function groupAddMenuItems(items) {
|
|
507
|
-
const byCategory = /* @__PURE__ */ new Map();
|
|
508
|
-
for (const item of items) {
|
|
509
|
-
const sub = item.subCategory ?? null;
|
|
510
|
-
let subMap = byCategory.get(item.category);
|
|
511
|
-
if (!subMap) {
|
|
512
|
-
subMap = /* @__PURE__ */ new Map();
|
|
513
|
-
byCategory.set(item.category, subMap);
|
|
514
|
-
}
|
|
515
|
-
const list = subMap.get(sub) ?? [];
|
|
516
|
-
list.push(item);
|
|
517
|
-
subMap.set(sub, list);
|
|
518
|
-
}
|
|
519
|
-
return [...byCategory.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, subMap]) => ({
|
|
520
|
-
category,
|
|
521
|
-
subCategories: [...subMap.entries()].sort(([a], [b]) => {
|
|
522
|
-
const sa = a ?? "";
|
|
523
|
-
const sb = b ?? "";
|
|
524
|
-
return sa.localeCompare(sb);
|
|
525
|
-
}).map(([subCategory, subItems]) => ({
|
|
526
|
-
subCategory,
|
|
527
|
-
items: subItems.sort((a, b) => a.title.localeCompare(b.title))
|
|
528
|
-
}))
|
|
529
|
-
}));
|
|
530
|
-
}
|
|
531
606
|
async function listAddMenuItems(projectRoot) {
|
|
532
607
|
const addMenuDir = path.join(projectRoot, ADD_MENU_DIR);
|
|
533
608
|
const warnings = [];
|
|
@@ -595,6 +670,65 @@ async function listAddMenuItems(projectRoot) {
|
|
|
595
670
|
warnings
|
|
596
671
|
};
|
|
597
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
|
+
}
|
|
598
732
|
const CATALOG = [
|
|
599
733
|
{
|
|
600
734
|
pluginName: "wix-server-client",
|
|
@@ -771,6 +905,26 @@ function buildLegacyPluginManifestPromptSection(selectedPlugins) {
|
|
|
771
905
|
}
|
|
772
906
|
function buildRouteMigrationPromptSection(routeMigration) {
|
|
773
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
|
+
}
|
|
774
928
|
return [
|
|
775
929
|
"",
|
|
776
930
|
"## Planned route migration (apply only if user request requires it)",
|
|
@@ -783,6 +937,35 @@ function buildRouteMigrationPromptSection(routeMigration) {
|
|
|
783
937
|
""
|
|
784
938
|
].join("\n");
|
|
785
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
|
+
}
|
|
786
969
|
function buildNonVisualPrompt(config, notes, imagePath) {
|
|
787
970
|
const lines = [
|
|
788
971
|
`Project directory: ${config.projectDir}`,
|
|
@@ -871,6 +1054,8 @@ function parseNotesField(notes) {
|
|
|
871
1054
|
annotations: j.annotations,
|
|
872
1055
|
previewNavLog,
|
|
873
1056
|
addMenuAttachments: j.addMenuAttachments,
|
|
1057
|
+
pageAssets: j.pageAssets,
|
|
1058
|
+
pageComponents: j.pageComponents,
|
|
874
1059
|
routeMigration: j.routeMigration
|
|
875
1060
|
},
|
|
876
1061
|
plain: ""
|
|
@@ -883,6 +1068,8 @@ function parseNotesField(notes) {
|
|
|
883
1068
|
structured: {
|
|
884
1069
|
annotations: j.annotations,
|
|
885
1070
|
addMenuAttachments: j.addMenuAttachments,
|
|
1071
|
+
pageAssets: j.pageAssets,
|
|
1072
|
+
pageComponents: j.pageComponents,
|
|
886
1073
|
routeMigration: j.routeMigration
|
|
887
1074
|
},
|
|
888
1075
|
structuredVideo: null,
|
|
@@ -893,6 +1080,26 @@ function parseNotesField(notes) {
|
|
|
893
1080
|
}
|
|
894
1081
|
return empty();
|
|
895
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
|
+
}
|
|
896
1103
|
function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, attachmentPathsByAnnotationId, addMenu = { itemById: /* @__PURE__ */ new Map() }) {
|
|
897
1104
|
const preamble = [
|
|
898
1105
|
`Project directory: ${config.projectDir}`,
|
|
@@ -909,9 +1116,20 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
|
|
|
909
1116
|
"- The annotation instructions and attachments are listed below in text — do NOT rely on screenshot text.",
|
|
910
1117
|
"",
|
|
911
1118
|
ANNOTATION_TARGETING_RULES,
|
|
912
|
-
""
|
|
1119
|
+
"",
|
|
1120
|
+
...visualPromptHasAttachments(attachmentPathsByAnnotationId) ? [buildVisualAttachmentRules(config.projectDir), ""] : []
|
|
913
1121
|
];
|
|
914
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
|
+
}
|
|
915
1133
|
const routeMigration = parsed.structured?.routeMigration ?? void 0;
|
|
916
1134
|
const requestAddMenu = parsed.structured?.addMenuAttachments ?? [];
|
|
917
1135
|
if (requestAddMenu.length > 0) {
|
|
@@ -938,16 +1156,7 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
|
|
|
938
1156
|
appendLegacyBindingLines(body, ann, ann.id);
|
|
939
1157
|
}
|
|
940
1158
|
body.push(`Instruction: ${ann.instruction.trim()}`);
|
|
941
|
-
|
|
942
|
-
body.push(
|
|
943
|
-
"Attachments (read these files; they belong to this annotation only):"
|
|
944
|
-
);
|
|
945
|
-
for (const p of paths) {
|
|
946
|
-
body.push(`- ${p}`);
|
|
947
|
-
}
|
|
948
|
-
} else {
|
|
949
|
-
body.push("Attachments: (none)");
|
|
950
|
-
}
|
|
1159
|
+
appendVisualAttachmentLines(body, paths);
|
|
951
1160
|
body.push("");
|
|
952
1161
|
}
|
|
953
1162
|
} else {
|
|
@@ -982,7 +1191,8 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
982
1191
|
"",
|
|
983
1192
|
...navLines,
|
|
984
1193
|
ANNOTATION_TARGETING_RULES,
|
|
985
|
-
""
|
|
1194
|
+
"",
|
|
1195
|
+
...visualPromptHasAttachments(attachmentPathsByPin) ? [buildVisualAttachmentRules(config.projectDir), ""] : []
|
|
986
1196
|
];
|
|
987
1197
|
if (!sv || sv.annotations.length === 0) {
|
|
988
1198
|
return [
|
|
@@ -996,6 +1206,16 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
996
1206
|
pinById.set(a.id, String(i + 1));
|
|
997
1207
|
}
|
|
998
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
|
+
}
|
|
999
1219
|
const routeMigration = sv.routeMigration;
|
|
1000
1220
|
const requestAddMenu = sv.addMenuAttachments ?? [];
|
|
1001
1221
|
if (requestAddMenu.length > 0) {
|
|
@@ -1036,10 +1256,7 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
1036
1256
|
body.push(` Page path at this pin: ${ann.pagePathAtTime}`);
|
|
1037
1257
|
}
|
|
1038
1258
|
if (paths.length > 0) {
|
|
1039
|
-
body
|
|
1040
|
-
" Attachments (read these files; they belong to this pin only):"
|
|
1041
|
-
);
|
|
1042
|
-
for (const p of paths) body.push(` - ${p}`);
|
|
1259
|
+
appendVisualAttachmentLines(body, paths, " ");
|
|
1043
1260
|
} else {
|
|
1044
1261
|
body.push(" Attachments: (none)");
|
|
1045
1262
|
}
|
|
@@ -1054,6 +1271,19 @@ function persistFile(file, destDir, fileName) {
|
|
|
1054
1271
|
fs$1.copyFileSync(file.path, dest);
|
|
1055
1272
|
return dest;
|
|
1056
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
|
+
}
|
|
1057
1287
|
const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHandler(async function* (input) {
|
|
1058
1288
|
const projectDir = process.cwd();
|
|
1059
1289
|
const buildFolder = path.join(projectDir, "build");
|
|
@@ -1067,7 +1297,8 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1067
1297
|
const isVisual = !!input.visualTask;
|
|
1068
1298
|
const catalog = await listAddMenuItems(projectDir);
|
|
1069
1299
|
const addMenu = {
|
|
1070
|
-
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
|
|
1071
1302
|
};
|
|
1072
1303
|
let promptContent;
|
|
1073
1304
|
if (isVideo) {
|
|
@@ -1093,16 +1324,7 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1093
1324
|
markersOnly: markers ? persistFile(markers, frameDir, "markers-only.png") : ""
|
|
1094
1325
|
});
|
|
1095
1326
|
}
|
|
1096
|
-
const attachmentPathsByPin =
|
|
1097
|
-
for (const [key, file] of Object.entries(extra)) {
|
|
1098
|
-
if (!key.startsWith("attachment_")) continue;
|
|
1099
|
-
const pinId = key.split("_")[1];
|
|
1100
|
-
const attDir = path.join(taskDir, `annotation-${pinId}`);
|
|
1101
|
-
const filePath = persistFile(file, attDir);
|
|
1102
|
-
const existing = attachmentPathsByPin.get(pinId) ?? [];
|
|
1103
|
-
existing.push(filePath);
|
|
1104
|
-
attachmentPathsByPin.set(pinId, existing);
|
|
1105
|
-
}
|
|
1327
|
+
const attachmentPathsByPin = persistExtraFileAttachments(taskDir, extra);
|
|
1106
1328
|
promptContent = buildVisualPromptVideo(
|
|
1107
1329
|
config,
|
|
1108
1330
|
input.pageRoute ?? "/",
|
|
@@ -1124,16 +1346,10 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1124
1346
|
)
|
|
1125
1347
|
};
|
|
1126
1348
|
const extraFiles = input.extraFiles ?? {};
|
|
1127
|
-
const attachmentPathsByAnnotationId =
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
const attDir = path.join(taskDir, `annotation-${pinId}`);
|
|
1132
|
-
const filePath = persistFile(file, attDir);
|
|
1133
|
-
const existing = attachmentPathsByAnnotationId.get(pinId) ?? [];
|
|
1134
|
-
existing.push(filePath);
|
|
1135
|
-
attachmentPathsByAnnotationId.set(pinId, existing);
|
|
1136
|
-
}
|
|
1349
|
+
const attachmentPathsByAnnotationId = persistExtraFileAttachments(
|
|
1350
|
+
taskDir,
|
|
1351
|
+
extraFiles
|
|
1352
|
+
);
|
|
1137
1353
|
promptContent = buildVisualPromptDual(
|
|
1138
1354
|
config,
|
|
1139
1355
|
input.pageRoute ?? "/",
|
|
@@ -1155,7 +1371,10 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1155
1371
|
}
|
|
1156
1372
|
yield { type: "status", message: "Running agent..." };
|
|
1157
1373
|
const systemPrompt = loadAgentKitSystemPrompt(projectDir);
|
|
1158
|
-
const routeKey =
|
|
1374
|
+
const routeKey = resolveAgentContextKey({
|
|
1375
|
+
pageRoute: input.pageRoute ?? "/",
|
|
1376
|
+
renderedUrl: input.renderedUrl
|
|
1377
|
+
});
|
|
1159
1378
|
const sessionPlan = await getOrCreateSessionIdForRoute(
|
|
1160
1379
|
projectDir,
|
|
1161
1380
|
routeKey
|
|
@@ -1176,6 +1395,7 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1176
1395
|
persistSession: true,
|
|
1177
1396
|
systemPrompt
|
|
1178
1397
|
};
|
|
1398
|
+
const cancelToken = beginCancellableAgentTask(routeKey);
|
|
1179
1399
|
try {
|
|
1180
1400
|
async function* streamAgentQuery(opts) {
|
|
1181
1401
|
for await (const message of query({
|
|
@@ -1185,6 +1405,9 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1185
1405
|
...opts
|
|
1186
1406
|
}
|
|
1187
1407
|
})) {
|
|
1408
|
+
if (isAgentTaskCancelled(routeKey)) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1188
1411
|
for (const chunk of transformSDKMessage(message)) {
|
|
1189
1412
|
yield chunk;
|
|
1190
1413
|
}
|
|
@@ -1226,10 +1449,24 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
1226
1449
|
}
|
|
1227
1450
|
}
|
|
1228
1451
|
} finally {
|
|
1452
|
+
endCancellableAgentTask(routeKey, cancelToken);
|
|
1229
1453
|
endRouteQuery(routeKey);
|
|
1230
1454
|
}
|
|
1231
1455
|
yield { type: "done" };
|
|
1232
1456
|
});
|
|
1457
|
+
const cancelAgentTaskAction = makeJayQuery(
|
|
1458
|
+
"aiditor.cancelAgentTask"
|
|
1459
|
+
).withHandler(async (input) => {
|
|
1460
|
+
const routeKey = resolveAgentContextKey({
|
|
1461
|
+
pageRoute: input.pageRoute ?? "/",
|
|
1462
|
+
renderedUrl: input.renderedUrl
|
|
1463
|
+
});
|
|
1464
|
+
const cancelled = cancelAgentTask(routeKey);
|
|
1465
|
+
if (!cancelled) {
|
|
1466
|
+
forceReleaseAgentRoute(routeKey);
|
|
1467
|
+
}
|
|
1468
|
+
return { cancelled: true };
|
|
1469
|
+
});
|
|
1233
1470
|
class AddPagePromptLoadError extends Error {
|
|
1234
1471
|
constructor(message) {
|
|
1235
1472
|
super(message);
|
|
@@ -2055,7 +2292,7 @@ When the image is a formal Figma **design definitions** board, apply these rules
|
|
|
2055
2292
|
${designDefinitionsBody}`;
|
|
2056
2293
|
});
|
|
2057
2294
|
}
|
|
2058
|
-
const
|
|
2295
|
+
const briefFillModelOverride = process.env.AIDITOR_BRIEF_FILL_MODEL?.trim();
|
|
2059
2296
|
const ALLOWED_IMAGE_MIMES = /* @__PURE__ */ new Set([
|
|
2060
2297
|
"image/png",
|
|
2061
2298
|
"image/jpeg",
|
|
@@ -2218,7 +2455,7 @@ async function runBriefFillAgentQuery(input) {
|
|
|
2218
2455
|
maxTurns: BRIEF_FILL_MAX_TURNS,
|
|
2219
2456
|
persistSession: false,
|
|
2220
2457
|
systemPrompt,
|
|
2221
|
-
model:
|
|
2458
|
+
...briefFillModelOverride ? { model: briefFillModelOverride } : void 0,
|
|
2222
2459
|
thinking: { type: "disabled" }
|
|
2223
2460
|
}
|
|
2224
2461
|
})) {
|
|
@@ -2648,10 +2885,10 @@ function isValidInstallSpec(spec) {
|
|
|
2648
2885
|
function getPluginSourcesPath(projectRoot) {
|
|
2649
2886
|
return path.join(projectRoot, ".aiditor", "plugin-sources.yaml");
|
|
2650
2887
|
}
|
|
2651
|
-
function parsePluginSourcesYaml(
|
|
2888
|
+
function parsePluginSourcesYaml(yaml2) {
|
|
2652
2889
|
const sources = [];
|
|
2653
2890
|
let current = null;
|
|
2654
|
-
for (const line of
|
|
2891
|
+
for (const line of yaml2.split("\n")) {
|
|
2655
2892
|
const itemMatch = line.match(/^ - pluginName:\s*(.+)$/);
|
|
2656
2893
|
if (itemMatch) {
|
|
2657
2894
|
if (current?.pluginName && current.packageName && current.installSpec) {
|
|
@@ -2867,6 +3104,41 @@ function resolveYarnAddInvocation(projectDir, spec) {
|
|
|
2867
3104
|
cwd: resolvedProject
|
|
2868
3105
|
};
|
|
2869
3106
|
}
|
|
3107
|
+
function resolvePackageScriptInvocation(projectDir, scriptName) {
|
|
3108
|
+
const resolvedProject = path.resolve(projectDir);
|
|
3109
|
+
const workspaceRoot = findYarnWorkspaceRoot(resolvedProject);
|
|
3110
|
+
const root = workspaceRoot ?? resolvedProject;
|
|
3111
|
+
const runner = resolveYarnRunner(root);
|
|
3112
|
+
const projectPkg = readPackageJson(resolvedProject);
|
|
3113
|
+
const rootPkg = readPackageJson(root);
|
|
3114
|
+
const usesYarn = Boolean(rootPkg?.packageManager?.startsWith("yarn@")) || fs$1.existsSync(path.join(root, "yarn.lock")) || fs$1.existsSync(path.join(resolvedProject, "yarn.lock"));
|
|
3115
|
+
if (usesYarn) {
|
|
3116
|
+
const useWorkspaceCommand = workspaceRoot !== void 0 && path.resolve(root) !== resolvedProject && hasYarnWorkspaces(rootPkg) && typeof projectPkg?.name === "string" && projectPkg.name.length > 0;
|
|
3117
|
+
if (useWorkspaceCommand) {
|
|
3118
|
+
return {
|
|
3119
|
+
command: runner.command,
|
|
3120
|
+
args: [
|
|
3121
|
+
...runner.prefixArgs,
|
|
3122
|
+
"workspace",
|
|
3123
|
+
projectPkg.name,
|
|
3124
|
+
scriptName
|
|
3125
|
+
],
|
|
3126
|
+
cwd: root
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
const args = runner.prefixArgs.length > 0 ? [...runner.prefixArgs, scriptName] : [scriptName];
|
|
3130
|
+
return {
|
|
3131
|
+
command: runner.command,
|
|
3132
|
+
args,
|
|
3133
|
+
cwd: resolvedProject
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
return {
|
|
3137
|
+
command: "npm",
|
|
3138
|
+
args: ["run", scriptName],
|
|
3139
|
+
cwd: resolvedProject
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
2870
3142
|
function parseSemver(version) {
|
|
2871
3143
|
const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
2872
3144
|
if (!m) return null;
|
|
@@ -2885,6 +3157,57 @@ function isNewerSemver(latest, installed) {
|
|
|
2885
3157
|
if (!a || !b) return false;
|
|
2886
3158
|
return compareSemver(a, b) > 0;
|
|
2887
3159
|
}
|
|
3160
|
+
async function* spawnCommandStream(command, args, cwd, options) {
|
|
3161
|
+
const child = spawn(command, args, {
|
|
3162
|
+
cwd,
|
|
3163
|
+
shell: options?.shell ?? false,
|
|
3164
|
+
env: process.env
|
|
3165
|
+
});
|
|
3166
|
+
const stdoutQueue = [];
|
|
3167
|
+
const stderrQueue = [];
|
|
3168
|
+
let closeCode = null;
|
|
3169
|
+
let spawnError;
|
|
3170
|
+
let notify;
|
|
3171
|
+
const wake = () => notify?.();
|
|
3172
|
+
child.stdout.on("data", (chunk) => {
|
|
3173
|
+
const text = chunk.toString();
|
|
3174
|
+
process.stdout.write(text);
|
|
3175
|
+
stdoutQueue.push({ type: "stdout", text });
|
|
3176
|
+
wake();
|
|
3177
|
+
});
|
|
3178
|
+
child.stderr.on("data", (chunk) => {
|
|
3179
|
+
const text = chunk.toString();
|
|
3180
|
+
process.stderr.write(text);
|
|
3181
|
+
stderrQueue.push({ type: "stderr", text });
|
|
3182
|
+
wake();
|
|
3183
|
+
});
|
|
3184
|
+
child.on("close", (code) => {
|
|
3185
|
+
closeCode = code;
|
|
3186
|
+
wake();
|
|
3187
|
+
});
|
|
3188
|
+
child.on("error", (err) => {
|
|
3189
|
+
spawnError = err.message;
|
|
3190
|
+
closeCode = 1;
|
|
3191
|
+
wake();
|
|
3192
|
+
});
|
|
3193
|
+
while (closeCode === null || stdoutQueue.length > 0 || stderrQueue.length > 0) {
|
|
3194
|
+
while (stdoutQueue.length > 0) {
|
|
3195
|
+
yield stdoutQueue.shift();
|
|
3196
|
+
}
|
|
3197
|
+
while (stderrQueue.length > 0) {
|
|
3198
|
+
yield stderrQueue.shift();
|
|
3199
|
+
}
|
|
3200
|
+
if (closeCode !== null) break;
|
|
3201
|
+
await new Promise((resolve) => {
|
|
3202
|
+
notify = resolve;
|
|
3203
|
+
});
|
|
3204
|
+
notify = void 0;
|
|
3205
|
+
}
|
|
3206
|
+
if (spawnError) {
|
|
3207
|
+
yield { type: "stderr", text: spawnError };
|
|
3208
|
+
}
|
|
3209
|
+
yield { type: "close", code: closeCode };
|
|
3210
|
+
}
|
|
2888
3211
|
async function spawnCommand(command, args, cwd, options) {
|
|
2889
3212
|
return new Promise((resolve) => {
|
|
2890
3213
|
const child = spawn(command, args, {
|
|
@@ -3293,24 +3616,61 @@ function pluginNameFromItemId(itemId) {
|
|
|
3293
3616
|
if (colon <= 0) return null;
|
|
3294
3617
|
return itemId.slice(0, colon);
|
|
3295
3618
|
}
|
|
3296
|
-
async function
|
|
3297
|
-
const
|
|
3298
|
-
const contractName = contractNameFromItemId(item.id);
|
|
3299
|
-
if (!pluginName || !contractName) return null;
|
|
3300
|
-
const contractPath = path.join(
|
|
3619
|
+
async function resolveContractFilePath(projectRoot, input) {
|
|
3620
|
+
const materializedPath = path.join(
|
|
3301
3621
|
projectRoot,
|
|
3302
3622
|
"agent-kit",
|
|
3303
3623
|
"materialized-contracts",
|
|
3304
|
-
pluginName,
|
|
3305
|
-
`${contractName}.jay-contract`
|
|
3624
|
+
input.pluginName,
|
|
3625
|
+
`${input.contractName}.jay-contract`
|
|
3306
3626
|
);
|
|
3307
|
-
|
|
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;
|
|
3308
3664
|
try {
|
|
3309
|
-
|
|
3665
|
+
const contractPath = await resolveContractFilePath(projectRoot, {
|
|
3666
|
+
pluginName,
|
|
3667
|
+
contractName
|
|
3668
|
+
});
|
|
3669
|
+
yaml2 = await fs.readFile(contractPath, "utf-8");
|
|
3310
3670
|
} catch {
|
|
3311
3671
|
return null;
|
|
3312
3672
|
}
|
|
3313
|
-
return parseContractParamsFromYaml(
|
|
3673
|
+
return parseContractParamsFromYaml(yaml2, contractName, pluginName);
|
|
3314
3674
|
}
|
|
3315
3675
|
const listAddMenuItemsAction = makeJayQuery("aiditor.listAddMenuItems").withServices(DEV_SERVER_SERVICE).withHandler(async () => {
|
|
3316
3676
|
const projectRoot = process.cwd();
|
|
@@ -3642,6 +4002,525 @@ const writePluginSourceAction = makeJayQuery("aiditor.writePluginSource").withSe
|
|
|
3642
4002
|
await writePluginSource(projectRoot, input);
|
|
3643
4003
|
return { ok: true };
|
|
3644
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
|
+
);
|
|
4459
|
+
const AIDITOR_PUBLISH_SCRIPT = "aiditor:publish";
|
|
4460
|
+
async function projectHasAiditorPublishScript(projectRoot) {
|
|
4461
|
+
try {
|
|
4462
|
+
const raw = await fs.readFile(
|
|
4463
|
+
path.join(projectRoot, "package.json"),
|
|
4464
|
+
"utf-8"
|
|
4465
|
+
);
|
|
4466
|
+
const pkg = JSON.parse(raw);
|
|
4467
|
+
const script = pkg.scripts?.[AIDITOR_PUBLISH_SCRIPT];
|
|
4468
|
+
return typeof script === "string" && script.trim().length > 0;
|
|
4469
|
+
} catch {
|
|
4470
|
+
return false;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
async function* streamAiditorPublishScript(projectRoot) {
|
|
4474
|
+
const hasScript = await projectHasAiditorPublishScript(projectRoot);
|
|
4475
|
+
if (!hasScript) {
|
|
4476
|
+
yield {
|
|
4477
|
+
type: "error",
|
|
4478
|
+
message: `package.json is missing an "${AIDITOR_PUBLISH_SCRIPT}" script.`
|
|
4479
|
+
};
|
|
4480
|
+
return;
|
|
4481
|
+
}
|
|
4482
|
+
const inv = resolvePackageScriptInvocation(
|
|
4483
|
+
projectRoot,
|
|
4484
|
+
AIDITOR_PUBLISH_SCRIPT
|
|
4485
|
+
);
|
|
4486
|
+
const commandLabel = [inv.command, ...inv.args].join(" ");
|
|
4487
|
+
yield {
|
|
4488
|
+
type: "output",
|
|
4489
|
+
stream: "stdout",
|
|
4490
|
+
text: `> ${commandLabel}
|
|
4491
|
+
`
|
|
4492
|
+
};
|
|
4493
|
+
for await (const chunk of spawnCommandStream(inv.command, inv.args, inv.cwd, {
|
|
4494
|
+
shell: inv.command === "npm"
|
|
4495
|
+
})) {
|
|
4496
|
+
if (chunk.type === "stdout" || chunk.type === "stderr") {
|
|
4497
|
+
yield { type: "output", stream: chunk.type, text: chunk.text };
|
|
4498
|
+
continue;
|
|
4499
|
+
}
|
|
4500
|
+
yield {
|
|
4501
|
+
type: "complete",
|
|
4502
|
+
success: chunk.code === 0,
|
|
4503
|
+
code: chunk.code
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
const checkAiditorPublishAction = makeJayQuery(
|
|
4508
|
+
"aiditor.checkAiditorPublish"
|
|
4509
|
+
).withHandler(async () => {
|
|
4510
|
+
const projectRoot = process.cwd();
|
|
4511
|
+
const hasPublishScript = await projectHasAiditorPublishScript(projectRoot);
|
|
4512
|
+
return { hasPublishScript };
|
|
4513
|
+
});
|
|
4514
|
+
const runAiditorPublishAction = makeJayStream(
|
|
4515
|
+
"aiditor.runAiditorPublish"
|
|
4516
|
+
).withHandler(async function* () {
|
|
4517
|
+
const projectRoot = process.cwd();
|
|
4518
|
+
for await (const chunk of streamAiditorPublishScript(projectRoot)) {
|
|
4519
|
+
yield chunk;
|
|
4520
|
+
if (chunk.type === "error") return;
|
|
4521
|
+
}
|
|
4522
|
+
yield { type: "done" };
|
|
4523
|
+
});
|
|
3645
4524
|
const AIDITOR_ADD_MENU_DOC = "aiditor-add-menu.md";
|
|
3646
4525
|
const INSTRUCTIONS_LINK_LINE = "- [Add Menu integration](./aiditor-add-menu.md) — contribute catalog items for AIditor";
|
|
3647
4526
|
async function resolveTemplateDir(fromModuleUrl) {
|
|
@@ -3733,12 +4612,17 @@ const page = makeJayStackComponent().withProps();
|
|
|
3733
4612
|
export {
|
|
3734
4613
|
page as aiditorPage,
|
|
3735
4614
|
aiditorShell,
|
|
4615
|
+
cancelAgentTaskAction,
|
|
3736
4616
|
checkAddPageRouteAction,
|
|
4617
|
+
checkAiditorPublishAction,
|
|
3737
4618
|
checkPageAddPageBriefAction,
|
|
3738
4619
|
checkPluginUpdateAction,
|
|
3739
4620
|
ensureProjectPluginAction,
|
|
3740
4621
|
generateAddPageBriefFromImageAction,
|
|
3741
4622
|
getAiditorBootstrap,
|
|
4623
|
+
getContractInspectorTagsAction,
|
|
4624
|
+
getPageAssetsAction,
|
|
4625
|
+
getPageMetaAction,
|
|
3742
4626
|
getPageParamsAction,
|
|
3743
4627
|
getPluginSetupStatusAction,
|
|
3744
4628
|
getProjectInfoAction,
|
|
@@ -3751,13 +4635,17 @@ export {
|
|
|
3751
4635
|
reclassifyAddPageAssetAction,
|
|
3752
4636
|
rerunPluginSetupAction,
|
|
3753
4637
|
resolveAddMenuContractParamsAction,
|
|
4638
|
+
runAiditorPublishAction,
|
|
3754
4639
|
saveAddPageDraftAction,
|
|
4640
|
+
savePageAssetsAction,
|
|
4641
|
+
savePageMetaAction,
|
|
3755
4642
|
setupAiditor,
|
|
3756
4643
|
startAddPageRequestAction,
|
|
3757
4644
|
submitAddPageAction,
|
|
3758
4645
|
submitTaskAction,
|
|
3759
4646
|
syncAddMenuAttachmentsAction,
|
|
3760
4647
|
syncAddPagePluginManifestAction,
|
|
4648
|
+
syncDetectedPageAssetsAction,
|
|
3761
4649
|
uploadAddPageAssetAction,
|
|
3762
4650
|
writePluginSourceAction
|
|
3763
4651
|
};
|