@jay-framework/aiditor 0.18.4 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/list-add-menu-items.jay-action +9 -0
- package/dist/actions/list-add-menu-items.jay-action.d.ts +3 -0
- package/dist/actions/resolve-add-menu-contract-params.jay-action +12 -0
- package/dist/actions/resolve-add-menu-contract-params.jay-action.d.ts +5 -0
- package/dist/actions/sync-add-menu-attachments.jay-action +13 -0
- package/dist/actions/sync-add-menu-attachments.jay-action.d.ts +10 -0
- package/dist/agent-kit-template/plugin/aiditor-add-menu.md +106 -0
- package/dist/index.client.d.ts +784 -5
- package/dist/index.client.js +1354 -564
- package/dist/index.d.ts +89 -729
- package/dist/index.js +981 -503
- package/dist/pages/aiditor/page.css +1350 -1
- package/dist/pages/aiditor/page.jay-html +1656 -484
- package/package.json +15 -7
- package/plugin.yaml +10 -0
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ 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
9
|
import { getLogger } from "@jay-framework/logger";
|
|
9
10
|
import { fileURLToPath } from "url";
|
|
10
11
|
import { spawn } from "child_process";
|
|
@@ -271,6 +272,517 @@ function buildChatPrompt(config, pageRoute, renderedUrl, userMessage) {
|
|
|
271
272
|
];
|
|
272
273
|
return lines.filter((l) => l !== null).join("\n");
|
|
273
274
|
}
|
|
275
|
+
const REJECTED_ITEM_FIELDS = [
|
|
276
|
+
"kind",
|
|
277
|
+
"parameters",
|
|
278
|
+
"component",
|
|
279
|
+
"allowedScopes"
|
|
280
|
+
];
|
|
281
|
+
function isRecord(value) {
|
|
282
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
283
|
+
}
|
|
284
|
+
function requiredString(obj, field, path2, errors) {
|
|
285
|
+
const value = obj[field];
|
|
286
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
287
|
+
errors.push({
|
|
288
|
+
path: `${path2}.${field}`,
|
|
289
|
+
message: "required non-empty string"
|
|
290
|
+
});
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
return value.trim();
|
|
294
|
+
}
|
|
295
|
+
function optionalString(obj, field) {
|
|
296
|
+
const value = obj[field];
|
|
297
|
+
if (value === void 0) return void 0;
|
|
298
|
+
if (typeof value !== "string") return void 0;
|
|
299
|
+
const trimmed = value.trim();
|
|
300
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
301
|
+
}
|
|
302
|
+
function validateAddMenuItem(raw, path2) {
|
|
303
|
+
const errors = [];
|
|
304
|
+
if (!isRecord(raw)) {
|
|
305
|
+
return {
|
|
306
|
+
item: null,
|
|
307
|
+
errors: [{ path: path2, message: "item must be an object" }]
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
for (const field of REJECTED_ITEM_FIELDS) {
|
|
311
|
+
if (field in raw) {
|
|
312
|
+
errors.push({
|
|
313
|
+
path: `${path2}.${field}`,
|
|
314
|
+
message: `field "${field}" is not allowed in Add Menu catalog items`
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const id = requiredString(raw, "id", path2, errors);
|
|
319
|
+
const title = requiredString(raw, "title", path2, errors);
|
|
320
|
+
const category = requiredString(raw, "category", path2, errors);
|
|
321
|
+
const prompt = requiredString(raw, "prompt", path2, errors);
|
|
322
|
+
if (errors.length > 0 || !id || !title || !category || !prompt) {
|
|
323
|
+
return { item: null, errors };
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
item: {
|
|
327
|
+
id,
|
|
328
|
+
title,
|
|
329
|
+
category,
|
|
330
|
+
prompt,
|
|
331
|
+
pluginName: optionalString(raw, "pluginName"),
|
|
332
|
+
packageName: optionalString(raw, "packageName"),
|
|
333
|
+
subCategory: optionalString(raw, "subCategory"),
|
|
334
|
+
thumbnail: optionalString(raw, "thumbnail")
|
|
335
|
+
},
|
|
336
|
+
errors
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function validateAddMenuCatalogFile(raw, sourcePath) {
|
|
340
|
+
const errors = [];
|
|
341
|
+
if (!isRecord(raw)) {
|
|
342
|
+
return {
|
|
343
|
+
file: null,
|
|
344
|
+
errors: [{ path: sourcePath, message: "catalog file must be an object" }]
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (!Array.isArray(raw.items)) {
|
|
348
|
+
return {
|
|
349
|
+
file: null,
|
|
350
|
+
errors: [
|
|
351
|
+
{ path: `${sourcePath}.items`, message: "items must be an array" }
|
|
352
|
+
]
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const items = [];
|
|
356
|
+
raw.items.forEach((entry, index) => {
|
|
357
|
+
const result = validateAddMenuItem(entry, `${sourcePath}.items[${index}]`);
|
|
358
|
+
errors.push(...result.errors);
|
|
359
|
+
if (result.item) items.push(result.item);
|
|
360
|
+
});
|
|
361
|
+
if (items.length === 0 && errors.length > 0) {
|
|
362
|
+
return { file: null, errors };
|
|
363
|
+
}
|
|
364
|
+
return { file: { items }, errors };
|
|
365
|
+
}
|
|
366
|
+
function parsePluginsIndexYaml(yaml) {
|
|
367
|
+
const entries = [];
|
|
368
|
+
let currentPlugin = null;
|
|
369
|
+
let inContracts = false;
|
|
370
|
+
for (const line of yaml.split("\n")) {
|
|
371
|
+
const pluginMatch = line.match(/^ - name:\s*(.+)$/);
|
|
372
|
+
if (pluginMatch && !line.startsWith(" ")) {
|
|
373
|
+
currentPlugin = pluginMatch[1].trim();
|
|
374
|
+
inContracts = false;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (line.trim() === "contracts:") {
|
|
378
|
+
inContracts = true;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (inContracts && currentPlugin) {
|
|
382
|
+
const contractMatch = line.match(/^ - name:\s*(.+)$/);
|
|
383
|
+
if (contractMatch) {
|
|
384
|
+
entries.push({
|
|
385
|
+
pluginName: currentPlugin,
|
|
386
|
+
contractName: contractMatch[1].trim()
|
|
387
|
+
});
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const descMatch = line.match(/^ description:\s*(.+)$/);
|
|
391
|
+
if (descMatch && entries.length > 0) {
|
|
392
|
+
const last = entries[entries.length - 1];
|
|
393
|
+
if (last.pluginName === currentPlugin) {
|
|
394
|
+
last.description = descMatch[1].trim();
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const typeMatch = line.match(/^ type:\s*(.+)$/);
|
|
399
|
+
if (typeMatch && entries.length > 0) {
|
|
400
|
+
const last = entries[entries.length - 1];
|
|
401
|
+
if (last.pluginName === currentPlugin) {
|
|
402
|
+
last.isDynamic = typeMatch[1].trim() === "dynamic";
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (line.match(/^ actions:/) || line.match(/^ routes:/)) {
|
|
407
|
+
inContracts = false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return entries;
|
|
411
|
+
}
|
|
412
|
+
function packageNameToPluginName(packageName) {
|
|
413
|
+
const prefix = "@jay-framework/";
|
|
414
|
+
if (packageName.startsWith(prefix)) {
|
|
415
|
+
return packageName.slice(prefix.length);
|
|
416
|
+
}
|
|
417
|
+
return packageName;
|
|
418
|
+
}
|
|
419
|
+
async function readPackageJsonDeps(projectRoot) {
|
|
420
|
+
const raw = await fs.readFile(
|
|
421
|
+
path.join(projectRoot, "package.json"),
|
|
422
|
+
"utf-8"
|
|
423
|
+
);
|
|
424
|
+
const pkg = JSON.parse(raw);
|
|
425
|
+
return { ...pkg.devDependencies, ...pkg.dependencies };
|
|
426
|
+
}
|
|
427
|
+
async function getInstalledPlugins(projectRoot) {
|
|
428
|
+
const deps = await readPackageJsonDeps(projectRoot);
|
|
429
|
+
const installed = [];
|
|
430
|
+
for (const [packageName, spec] of Object.entries(deps)) {
|
|
431
|
+
if (!packageName.startsWith("@jay-framework/")) continue;
|
|
432
|
+
installed.push({
|
|
433
|
+
pluginName: packageNameToPluginName(packageName),
|
|
434
|
+
packageName,
|
|
435
|
+
installSpec: spec
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return installed;
|
|
439
|
+
}
|
|
440
|
+
async function isPluginInstalled(projectRoot, pluginName) {
|
|
441
|
+
const installed = await getInstalledPlugins(projectRoot);
|
|
442
|
+
return installed.some((p) => p.pluginName === pluginName);
|
|
443
|
+
}
|
|
444
|
+
async function parsePluginsIndexContracts(projectRoot) {
|
|
445
|
+
const indexPath = path.join(projectRoot, "agent-kit", "plugins-index.yaml");
|
|
446
|
+
try {
|
|
447
|
+
const yaml = await fs.readFile(indexPath, "utf-8");
|
|
448
|
+
return parsePluginsIndexYaml(yaml);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
451
|
+
if (code === "ENOENT") return [];
|
|
452
|
+
throw e;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async function parsePluginYamlContracts(projectRoot, pluginName) {
|
|
456
|
+
const pluginYamlPath = path.join(
|
|
457
|
+
projectRoot,
|
|
458
|
+
"node_modules",
|
|
459
|
+
"@jay-framework",
|
|
460
|
+
pluginName,
|
|
461
|
+
"plugin.yaml"
|
|
462
|
+
);
|
|
463
|
+
try {
|
|
464
|
+
const yaml = await fs.readFile(pluginYamlPath, "utf-8");
|
|
465
|
+
const entries = [];
|
|
466
|
+
let inContracts = false;
|
|
467
|
+
for (const line of yaml.split("\n")) {
|
|
468
|
+
if (line.trim() === "contracts:") {
|
|
469
|
+
inContracts = true;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (inContracts) {
|
|
473
|
+
const nameMatch = line.match(/^ - name:\s*(.+)$/);
|
|
474
|
+
if (nameMatch) {
|
|
475
|
+
entries.push({
|
|
476
|
+
pluginName,
|
|
477
|
+
contractName: nameMatch[1].trim()
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (line.match(/^actions:/) || line.match(/^routes:/)) {
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return entries;
|
|
486
|
+
} catch {
|
|
487
|
+
return [];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const ADD_MENU_DIR = path.join("agent-kit", "aiditor", "add-menu");
|
|
491
|
+
function packageNameFromItem(item) {
|
|
492
|
+
if (item.packageName) return item.packageName;
|
|
493
|
+
if (item.pluginName) return `@jay-framework/${item.pluginName}`;
|
|
494
|
+
const colon = item.id.indexOf(":");
|
|
495
|
+
if (colon > 0) {
|
|
496
|
+
return `@jay-framework/${item.id.slice(0, colon)}`;
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function pluginNameFromItem(item) {
|
|
501
|
+
if (item.pluginName) return item.pluginName;
|
|
502
|
+
const colon = item.id.indexOf(":");
|
|
503
|
+
if (colon > 0) return item.id.slice(0, colon);
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
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
|
+
async function listAddMenuItems(projectRoot) {
|
|
532
|
+
const addMenuDir = path.join(projectRoot, ADD_MENU_DIR);
|
|
533
|
+
const warnings = [];
|
|
534
|
+
let yamlFiles = [];
|
|
535
|
+
try {
|
|
536
|
+
const entries = await fs.readdir(addMenuDir);
|
|
537
|
+
yamlFiles = entries.filter((name) => name.endsWith(".yaml") || name.endsWith(".yml")).map((name) => path.join(addMenuDir, name)).sort((a, b) => a.localeCompare(b));
|
|
538
|
+
} catch (e) {
|
|
539
|
+
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
540
|
+
if (code === "ENOENT") {
|
|
541
|
+
return { items: [], categories: [], warnings: [] };
|
|
542
|
+
}
|
|
543
|
+
throw e;
|
|
544
|
+
}
|
|
545
|
+
const installed = await getInstalledPlugins(projectRoot);
|
|
546
|
+
const installedPackages = new Set(installed.map((p) => p.packageName));
|
|
547
|
+
const installedPlugins = new Set(installed.map((p) => p.pluginName));
|
|
548
|
+
const byId = /* @__PURE__ */ new Map();
|
|
549
|
+
for (const filePath of yamlFiles) {
|
|
550
|
+
const relPath = path.relative(projectRoot, filePath);
|
|
551
|
+
let rawContent;
|
|
552
|
+
try {
|
|
553
|
+
rawContent = await fs.readFile(filePath, "utf-8");
|
|
554
|
+
} catch {
|
|
555
|
+
warnings.push(`Could not read Add Menu catalog file: ${relPath}`);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
let parsed;
|
|
559
|
+
try {
|
|
560
|
+
parsed = load(rawContent);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
warnings.push(
|
|
563
|
+
`Invalid YAML in ${relPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
564
|
+
);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const validated = validateAddMenuCatalogFile(parsed, relPath);
|
|
568
|
+
if (validated.errors.length > 0) {
|
|
569
|
+
for (const err of validated.errors) {
|
|
570
|
+
warnings.push(`${err.path}: ${err.message}`);
|
|
571
|
+
}
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
for (const item of validated.file.items) {
|
|
575
|
+
const prev = byId.get(item.id);
|
|
576
|
+
if (prev) {
|
|
577
|
+
warnings.push(
|
|
578
|
+
`Duplicate Add Menu item id "${item.id}": "${prev.sourcePath}" overwritten by "${relPath}"`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
byId.set(item.id, { item, sourcePath: relPath });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const merged = [...byId.values()].map((entry) => entry.item);
|
|
585
|
+
const filtered = merged.filter((item) => {
|
|
586
|
+
const pkg = packageNameFromItem(item);
|
|
587
|
+
const plugin = pluginNameFromItem(item);
|
|
588
|
+
if (pkg && installedPackages.has(pkg)) return true;
|
|
589
|
+
if (plugin && installedPlugins.has(plugin)) return true;
|
|
590
|
+
return false;
|
|
591
|
+
});
|
|
592
|
+
return {
|
|
593
|
+
items: filtered.sort((a, b) => a.title.localeCompare(b.title)),
|
|
594
|
+
categories: groupAddMenuItems(filtered),
|
|
595
|
+
warnings
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
const CATALOG = [
|
|
599
|
+
{
|
|
600
|
+
pluginName: "wix-server-client",
|
|
601
|
+
packageName: "@jay-framework/wix-server-client",
|
|
602
|
+
description: "Wix API client and authentication",
|
|
603
|
+
kind: "service-only",
|
|
604
|
+
requires: [],
|
|
605
|
+
showInAddPagePicker: false,
|
|
606
|
+
configFiles: ["config/.wix.yaml"]
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
pluginName: "wix-cart",
|
|
610
|
+
packageName: "@jay-framework/wix-cart",
|
|
611
|
+
description: "Shopping cart headless components",
|
|
612
|
+
kind: "headless",
|
|
613
|
+
requires: ["wix-server-client"],
|
|
614
|
+
showInAddPagePicker: true
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
pluginName: "wix-stores",
|
|
618
|
+
packageName: "@jay-framework/wix-stores",
|
|
619
|
+
description: "Wix Stores product search, product page, categories",
|
|
620
|
+
kind: "headless",
|
|
621
|
+
requires: ["wix-server-client", "wix-cart"],
|
|
622
|
+
showInAddPagePicker: true,
|
|
623
|
+
configFiles: ["config/.wix-stores.yaml"]
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
pluginName: "wix-stores-v1",
|
|
627
|
+
packageName: "@jay-framework/wix-stores-v1",
|
|
628
|
+
description: "Wix Stores v1 catalog API",
|
|
629
|
+
kind: "headless",
|
|
630
|
+
requires: ["wix-server-client", "wix-cart"],
|
|
631
|
+
showInAddPagePicker: true
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
pluginName: "wix-data",
|
|
635
|
+
packageName: "@jay-framework/wix-data",
|
|
636
|
+
description: "Wix Data collections CMS",
|
|
637
|
+
kind: "headless",
|
|
638
|
+
requires: ["wix-server-client"],
|
|
639
|
+
showInAddPagePicker: true,
|
|
640
|
+
configFiles: ["config/.wix-data.yaml"]
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
pluginName: "wix-media",
|
|
644
|
+
packageName: "@jay-framework/wix-media",
|
|
645
|
+
description: "Wix media service (no page components)",
|
|
646
|
+
kind: "service-only",
|
|
647
|
+
requires: ["wix-server-client"],
|
|
648
|
+
showInAddPagePicker: true
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
pluginName: "ui-kit",
|
|
652
|
+
packageName: "@jay-framework/ui-kit",
|
|
653
|
+
description: "Jay UI kit headless components",
|
|
654
|
+
kind: "headless",
|
|
655
|
+
requires: [],
|
|
656
|
+
showInAddPagePicker: true
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
pluginName: "gemini-agent",
|
|
660
|
+
packageName: "@jay-framework/gemini-agent",
|
|
661
|
+
description: "Gemini AI agent plugin",
|
|
662
|
+
kind: "headless",
|
|
663
|
+
requires: [],
|
|
664
|
+
showInAddPagePicker: true,
|
|
665
|
+
configFiles: ["config/.gemini-agent.yaml"]
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
pluginName: "webmcp",
|
|
669
|
+
packageName: "@jay-framework/webmcp",
|
|
670
|
+
description: "Web MCP tooling",
|
|
671
|
+
kind: "tooling",
|
|
672
|
+
requires: [],
|
|
673
|
+
showInAddPagePicker: false
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
pluginName: "aiditor",
|
|
677
|
+
packageName: "@jay-framework/aiditor",
|
|
678
|
+
description: "AIditor self plugin",
|
|
679
|
+
kind: "tooling",
|
|
680
|
+
requires: [],
|
|
681
|
+
showInAddPagePicker: false
|
|
682
|
+
}
|
|
683
|
+
];
|
|
684
|
+
function getJayPluginCatalog() {
|
|
685
|
+
return CATALOG.map((e) => ({ ...e, requires: [...e.requires] }));
|
|
686
|
+
}
|
|
687
|
+
function getCatalogEntry(pluginName) {
|
|
688
|
+
return CATALOG.find((e) => e.pluginName === pluginName);
|
|
689
|
+
}
|
|
690
|
+
const DEFAULT_COMPONENT_KEYS = {
|
|
691
|
+
"product-page": "p",
|
|
692
|
+
"product-search": "search",
|
|
693
|
+
"cart-indicator": "cart",
|
|
694
|
+
"cart-page": "cartPage",
|
|
695
|
+
"category-list": "categories"
|
|
696
|
+
};
|
|
697
|
+
function getDefaultComponentKey(contractName) {
|
|
698
|
+
if (contractName in DEFAULT_COMPONENT_KEYS) {
|
|
699
|
+
return DEFAULT_COMPONENT_KEYS[contractName];
|
|
700
|
+
}
|
|
701
|
+
return contractName.charAt(0) || "c";
|
|
702
|
+
}
|
|
703
|
+
function pagePluginSelectionsToAttachments(selectedPlugins) {
|
|
704
|
+
const attachments = [];
|
|
705
|
+
for (const plugin of selectedPlugins) {
|
|
706
|
+
for (const contract of plugin.contracts) {
|
|
707
|
+
attachments.push({
|
|
708
|
+
itemId: `${plugin.pluginName}:${contract.contractName}`
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return attachments;
|
|
713
|
+
}
|
|
714
|
+
const ADD_MENU_Q3_RULE = `Add Menu usage (Q3 — follow strictly):
|
|
715
|
+
- Each attached Add Menu item describes a **resource in scope**, not a fixed number of DOM instances.
|
|
716
|
+
- Wire **only** attachments the user referenced in prose, \`@Title\` mentions, or marker instructions.
|
|
717
|
+
- Do **not** implement an attachment's prompt if the user did not reference it.
|
|
718
|
+
- One headless import per resource; multiple instances with different props when the brief requires.`;
|
|
719
|
+
function resolveAddMenuAttachments(input) {
|
|
720
|
+
if (input.addMenuAttachments?.length) return input.addMenuAttachments;
|
|
721
|
+
if (input.selectedPlugins?.length) {
|
|
722
|
+
return pagePluginSelectionsToAttachments(input.selectedPlugins);
|
|
723
|
+
}
|
|
724
|
+
return [];
|
|
725
|
+
}
|
|
726
|
+
function buildAddMenuAttachmentPromptSection(attachments, itemById, options) {
|
|
727
|
+
const scoped = options?.annotationId ? attachments.filter((a) => a.annotationId === options.annotationId) : attachments.filter((a) => !a.annotationId);
|
|
728
|
+
if (scoped.length === 0) return "";
|
|
729
|
+
const lines = ["", "## Add Menu attachments", "", ADD_MENU_Q3_RULE, ""];
|
|
730
|
+
for (const attachment of scoped) {
|
|
731
|
+
const item = itemById.get(attachment.itemId);
|
|
732
|
+
if (!item) {
|
|
733
|
+
lines.push(
|
|
734
|
+
`### ${attachment.itemId}`,
|
|
735
|
+
"",
|
|
736
|
+
"(catalog item not found)",
|
|
737
|
+
""
|
|
738
|
+
);
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const scope = attachment.annotationId ? `Marker #${attachment.annotationId}` : "Request-level";
|
|
742
|
+
lines.push(
|
|
743
|
+
`### ${item.title} (${item.id})`,
|
|
744
|
+
`Scope: ${scope}`,
|
|
745
|
+
"",
|
|
746
|
+
item.prompt.trim(),
|
|
747
|
+
""
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
return lines.join("\n");
|
|
751
|
+
}
|
|
752
|
+
function buildLegacyPluginManifestPromptSection(selectedPlugins) {
|
|
753
|
+
if (!selectedPlugins.length) return "";
|
|
754
|
+
const lines = [
|
|
755
|
+
"",
|
|
756
|
+
"## Page plugin manifest (legacy — prefer Add Menu attachments)",
|
|
757
|
+
"",
|
|
758
|
+
"| Plugin | Contract | Key |",
|
|
759
|
+
"| --- | --- | --- |"
|
|
760
|
+
];
|
|
761
|
+
for (const plugin of selectedPlugins) {
|
|
762
|
+
for (const contract of plugin.contracts) {
|
|
763
|
+
const key = contract.componentKey ?? getDefaultComponentKey(contract.contractName);
|
|
764
|
+
lines.push(
|
|
765
|
+
`| ${plugin.packageName} | ${contract.contractName} | ${key} |`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
lines.push("");
|
|
770
|
+
return lines.join("\n");
|
|
771
|
+
}
|
|
772
|
+
function buildRouteMigrationPromptSection(routeMigration) {
|
|
773
|
+
if (!routeMigration) return "";
|
|
774
|
+
return [
|
|
775
|
+
"",
|
|
776
|
+
"## Planned route migration (apply only if user request requires it)",
|
|
777
|
+
"",
|
|
778
|
+
`- Current route: \`${routeMigration.currentRoute}\``,
|
|
779
|
+
`- Planned route: \`${routeMigration.plannedRoute}\``,
|
|
780
|
+
`- Triggering Add Menu items: ${routeMigration.triggeringItemIds.join(", ")}`,
|
|
781
|
+
"",
|
|
782
|
+
"Migrate page files to the planned route only when the user's instruction uses the attached route-param components.",
|
|
783
|
+
""
|
|
784
|
+
].join("\n");
|
|
785
|
+
}
|
|
274
786
|
function buildNonVisualPrompt(config, notes, imagePath) {
|
|
275
787
|
const lines = [
|
|
276
788
|
`Project directory: ${config.projectDir}`,
|
|
@@ -290,6 +802,27 @@ function structuredAnnotationBindings(ann) {
|
|
|
290
802
|
if (ann.pluginBinding) return [ann.pluginBinding];
|
|
291
803
|
return [];
|
|
292
804
|
}
|
|
805
|
+
function appendAddMenuLinesForAnnotation(body, ann, itemById, indent = "") {
|
|
806
|
+
const attachments = ann.addMenuAttachments ?? [];
|
|
807
|
+
if (attachments.length === 0) return;
|
|
808
|
+
const section = buildAddMenuAttachmentPromptSection(
|
|
809
|
+
attachments.map((a) => ({
|
|
810
|
+
itemId: a.itemId,
|
|
811
|
+
annotationId: a.annotationId ?? ann.id
|
|
812
|
+
})),
|
|
813
|
+
itemById,
|
|
814
|
+
{ annotationId: ann.id }
|
|
815
|
+
);
|
|
816
|
+
if (!section.trim()) return;
|
|
817
|
+
for (const line of section.trim().split("\n")) {
|
|
818
|
+
body.push(`${indent}${line}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function appendLegacyBindingLines(body, ann, markerId, indent = "") {
|
|
822
|
+
const bindings = structuredAnnotationBindings(ann);
|
|
823
|
+
if (bindings.length === 0) return;
|
|
824
|
+
appendBindingPromptLines(body, bindings, markerId, indent);
|
|
825
|
+
}
|
|
293
826
|
function appendBindingPromptLines(body, bindings, markerId, indent = "") {
|
|
294
827
|
if (bindings.length === 0) return;
|
|
295
828
|
body.push(`${indent}Headless bindings:`);
|
|
@@ -336,7 +869,9 @@ function parseNotesField(notes) {
|
|
|
336
869
|
structured: null,
|
|
337
870
|
structuredVideo: {
|
|
338
871
|
annotations: j.annotations,
|
|
339
|
-
previewNavLog
|
|
872
|
+
previewNavLog,
|
|
873
|
+
addMenuAttachments: j.addMenuAttachments,
|
|
874
|
+
routeMigration: j.routeMigration
|
|
340
875
|
},
|
|
341
876
|
plain: ""
|
|
342
877
|
};
|
|
@@ -345,7 +880,11 @@ function parseNotesField(notes) {
|
|
|
345
880
|
(a) => a && typeof a === "object" && typeof a.id === "string" && typeof a.instruction === "string" && typeof a.mode === "string"
|
|
346
881
|
)) {
|
|
347
882
|
return {
|
|
348
|
-
structured: {
|
|
883
|
+
structured: {
|
|
884
|
+
annotations: j.annotations,
|
|
885
|
+
addMenuAttachments: j.addMenuAttachments,
|
|
886
|
+
routeMigration: j.routeMigration
|
|
887
|
+
},
|
|
349
888
|
structuredVideo: null,
|
|
350
889
|
plain: ""
|
|
351
890
|
};
|
|
@@ -354,7 +893,7 @@ function parseNotesField(notes) {
|
|
|
354
893
|
}
|
|
355
894
|
return empty();
|
|
356
895
|
}
|
|
357
|
-
function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, attachmentPathsByAnnotationId) {
|
|
896
|
+
function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, attachmentPathsByAnnotationId, addMenu = { itemById: /* @__PURE__ */ new Map() }) {
|
|
358
897
|
const preamble = [
|
|
359
898
|
`Project directory: ${config.projectDir}`,
|
|
360
899
|
"",
|
|
@@ -373,6 +912,18 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
|
|
|
373
912
|
""
|
|
374
913
|
];
|
|
375
914
|
const body = [];
|
|
915
|
+
const routeMigration = parsed.structured?.routeMigration ?? void 0;
|
|
916
|
+
const requestAddMenu = parsed.structured?.addMenuAttachments ?? [];
|
|
917
|
+
if (requestAddMenu.length > 0) {
|
|
918
|
+
const section = buildAddMenuAttachmentPromptSection(
|
|
919
|
+
requestAddMenu,
|
|
920
|
+
addMenu.itemById
|
|
921
|
+
);
|
|
922
|
+
if (section.trim()) body.push(section.trim(), "");
|
|
923
|
+
}
|
|
924
|
+
if (routeMigration) {
|
|
925
|
+
body.push(buildRouteMigrationPromptSection(routeMigration).trim(), "");
|
|
926
|
+
}
|
|
376
927
|
if (parsed.structured) {
|
|
377
928
|
body.push("Annotations (apply in order):", "");
|
|
378
929
|
const sorted = [...parsed.structured.annotations].sort(
|
|
@@ -382,7 +933,10 @@ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, att
|
|
|
382
933
|
const pin = ann.id;
|
|
383
934
|
const paths = attachmentPathsByAnnotationId.get(pin) ?? [];
|
|
384
935
|
body.push(`Annotation ${pin} (${ann.mode})`);
|
|
385
|
-
|
|
936
|
+
appendAddMenuLinesForAnnotation(body, ann, addMenu.itemById);
|
|
937
|
+
if (!ann.addMenuAttachments?.length) {
|
|
938
|
+
appendLegacyBindingLines(body, ann, ann.id);
|
|
939
|
+
}
|
|
386
940
|
body.push(`Instruction: ${ann.instruction.trim()}`);
|
|
387
941
|
if (paths.length > 0) {
|
|
388
942
|
body.push(
|
|
@@ -407,7 +961,7 @@ function pinOrder(annotations) {
|
|
|
407
961
|
return a.id.localeCompare(b.id, void 0, { sensitivity: "base" });
|
|
408
962
|
});
|
|
409
963
|
}
|
|
410
|
-
function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frames, parsed, attachmentPathsByPin) {
|
|
964
|
+
function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frames, parsed, attachmentPathsByPin, addMenu = { itemById: /* @__PURE__ */ new Map() }) {
|
|
411
965
|
const sv = parsed.structuredVideo;
|
|
412
966
|
const navLines = [];
|
|
413
967
|
if (sv?.previewNavLog && sv.previewNavLog.length > 0) {
|
|
@@ -441,7 +995,20 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
441
995
|
for (const [i, a] of pinOrder(sv.annotations).entries()) {
|
|
442
996
|
pinById.set(a.id, String(i + 1));
|
|
443
997
|
}
|
|
444
|
-
const body = [
|
|
998
|
+
const body = [];
|
|
999
|
+
const routeMigration = sv.routeMigration;
|
|
1000
|
+
const requestAddMenu = sv.addMenuAttachments ?? [];
|
|
1001
|
+
if (requestAddMenu.length > 0) {
|
|
1002
|
+
const section = buildAddMenuAttachmentPromptSection(
|
|
1003
|
+
requestAddMenu,
|
|
1004
|
+
addMenu.itemById
|
|
1005
|
+
);
|
|
1006
|
+
if (section.trim()) body.push(section.trim(), "");
|
|
1007
|
+
}
|
|
1008
|
+
if (routeMigration) {
|
|
1009
|
+
body.push(buildRouteMigrationPromptSection(routeMigration).trim(), "");
|
|
1010
|
+
}
|
|
1011
|
+
body.push("Per-moment captures:", "");
|
|
445
1012
|
for (const fr of frames) {
|
|
446
1013
|
body.push(
|
|
447
1014
|
`Moment ${fr.frameIndex + 1} — t=${fr.timeSec.toFixed(2)}s`,
|
|
@@ -455,12 +1022,10 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
|
|
|
455
1022
|
const pinNum = pinById.get(ann.id) ?? "?";
|
|
456
1023
|
const paths = attachmentPathsByPin.get(pinNum) ?? [];
|
|
457
1024
|
body.push(`- Pin ${pinNum} (id ${ann.id}) — ${ann.mode}`);
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
" "
|
|
463
|
-
);
|
|
1025
|
+
appendAddMenuLinesForAnnotation(body, ann, addMenu.itemById, " ");
|
|
1026
|
+
if (!ann.addMenuAttachments?.length) {
|
|
1027
|
+
appendLegacyBindingLines(body, ann, ann.id, " ");
|
|
1028
|
+
}
|
|
464
1029
|
body.push(` Instruction: ${ann.instruction.trim()}`);
|
|
465
1030
|
if (ann.previewUrlAtTime) {
|
|
466
1031
|
body.push(
|
|
@@ -500,6 +1065,10 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
500
1065
|
const parsed = parseNotesField(input.notes);
|
|
501
1066
|
const isVideo = input.visualTask === "5";
|
|
502
1067
|
const isVisual = !!input.visualTask;
|
|
1068
|
+
const catalog = await listAddMenuItems(projectDir);
|
|
1069
|
+
const addMenu = {
|
|
1070
|
+
itemById: new Map(catalog.items.map((item) => [item.id, item]))
|
|
1071
|
+
};
|
|
503
1072
|
let promptContent;
|
|
504
1073
|
if (isVideo) {
|
|
505
1074
|
let videoPath = "";
|
|
@@ -541,7 +1110,8 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
541
1110
|
videoPath,
|
|
542
1111
|
frames,
|
|
543
1112
|
parsed,
|
|
544
|
-
attachmentPathsByPin
|
|
1113
|
+
attachmentPathsByPin,
|
|
1114
|
+
addMenu
|
|
545
1115
|
);
|
|
546
1116
|
} else if (isVisual && input.screenshot_markers_only) {
|
|
547
1117
|
const ssDir = path.join(taskDir, "screenshots");
|
|
@@ -570,7 +1140,8 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
570
1140
|
input.renderedUrl ?? "",
|
|
571
1141
|
dual,
|
|
572
1142
|
parsed,
|
|
573
|
-
attachmentPathsByAnnotationId
|
|
1143
|
+
attachmentPathsByAnnotationId,
|
|
1144
|
+
addMenu
|
|
574
1145
|
);
|
|
575
1146
|
} else if (input.messageKind === "chat") {
|
|
576
1147
|
promptContent = buildChatPrompt(
|
|
@@ -606,164 +1177,59 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
|
|
|
606
1177
|
systemPrompt
|
|
607
1178
|
};
|
|
608
1179
|
try {
|
|
609
|
-
async function* streamAgentQuery(opts) {
|
|
610
|
-
for await (const message of query({
|
|
611
|
-
prompt: promptContent,
|
|
612
|
-
options: {
|
|
613
|
-
...sharedOptions,
|
|
614
|
-
...opts
|
|
615
|
-
}
|
|
616
|
-
})) {
|
|
617
|
-
for (const chunk of transformSDKMessage(message)) {
|
|
618
|
-
yield chunk;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
let didStaleResumeRetry = false;
|
|
623
|
-
try {
|
|
624
|
-
if (sessionPlan.useResume) {
|
|
625
|
-
yield* streamAgentQuery({ resume: sessionPlan.sessionId });
|
|
626
|
-
} else {
|
|
627
|
-
yield* streamAgentQuery({
|
|
628
|
-
sessionId: sessionPlan.sessionId,
|
|
629
|
-
title: routeKey
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
} catch (err) {
|
|
633
|
-
if (sessionPlan.useResume && !didStaleResumeRetry) {
|
|
634
|
-
didStaleResumeRetry = true;
|
|
635
|
-
const fresh = await replaceRouteSessionAfterStaleResume(
|
|
636
|
-
projectDir,
|
|
637
|
-
routeKey
|
|
638
|
-
);
|
|
639
|
-
try {
|
|
640
|
-
yield* streamAgentQuery({
|
|
641
|
-
sessionId: fresh.sessionId,
|
|
642
|
-
title: routeKey
|
|
643
|
-
});
|
|
644
|
-
} catch (err2) {
|
|
645
|
-
yield {
|
|
646
|
-
type: "error",
|
|
647
|
-
message: err2 instanceof Error ? err2.message : String(err2)
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
} else {
|
|
651
|
-
yield {
|
|
652
|
-
type: "error",
|
|
653
|
-
message: err instanceof Error ? err.message : String(err)
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
} finally {
|
|
658
|
-
endRouteQuery(routeKey);
|
|
659
|
-
}
|
|
660
|
-
yield { type: "done" };
|
|
661
|
-
});
|
|
662
|
-
const CATALOG = [
|
|
663
|
-
{
|
|
664
|
-
pluginName: "wix-server-client",
|
|
665
|
-
packageName: "@jay-framework/wix-server-client",
|
|
666
|
-
description: "Wix API client and authentication",
|
|
667
|
-
kind: "service-only",
|
|
668
|
-
requires: [],
|
|
669
|
-
showInAddPagePicker: false,
|
|
670
|
-
configFiles: ["config/.wix.yaml"]
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
pluginName: "wix-cart",
|
|
674
|
-
packageName: "@jay-framework/wix-cart",
|
|
675
|
-
description: "Shopping cart headless components",
|
|
676
|
-
kind: "headless",
|
|
677
|
-
requires: ["wix-server-client"],
|
|
678
|
-
showInAddPagePicker: true
|
|
679
|
-
},
|
|
680
|
-
{
|
|
681
|
-
pluginName: "wix-stores",
|
|
682
|
-
packageName: "@jay-framework/wix-stores",
|
|
683
|
-
description: "Wix Stores product search, product page, categories",
|
|
684
|
-
kind: "headless",
|
|
685
|
-
requires: ["wix-server-client", "wix-cart"],
|
|
686
|
-
showInAddPagePicker: true,
|
|
687
|
-
configFiles: ["config/.wix-stores.yaml"]
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
pluginName: "wix-stores-v1",
|
|
691
|
-
packageName: "@jay-framework/wix-stores-v1",
|
|
692
|
-
description: "Wix Stores v1 catalog API",
|
|
693
|
-
kind: "headless",
|
|
694
|
-
requires: ["wix-server-client", "wix-cart"],
|
|
695
|
-
showInAddPagePicker: true
|
|
696
|
-
},
|
|
697
|
-
{
|
|
698
|
-
pluginName: "wix-data",
|
|
699
|
-
packageName: "@jay-framework/wix-data",
|
|
700
|
-
description: "Wix Data collections CMS",
|
|
701
|
-
kind: "headless",
|
|
702
|
-
requires: ["wix-server-client"],
|
|
703
|
-
showInAddPagePicker: true,
|
|
704
|
-
configFiles: ["config/.wix-data.yaml"]
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
pluginName: "wix-media",
|
|
708
|
-
packageName: "@jay-framework/wix-media",
|
|
709
|
-
description: "Wix media service (no page components)",
|
|
710
|
-
kind: "service-only",
|
|
711
|
-
requires: ["wix-server-client"],
|
|
712
|
-
showInAddPagePicker: true
|
|
713
|
-
},
|
|
714
|
-
{
|
|
715
|
-
pluginName: "ui-kit",
|
|
716
|
-
packageName: "@jay-framework/ui-kit",
|
|
717
|
-
description: "Jay UI kit headless components",
|
|
718
|
-
kind: "headless",
|
|
719
|
-
requires: [],
|
|
720
|
-
showInAddPagePicker: true
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
pluginName: "gemini-agent",
|
|
724
|
-
packageName: "@jay-framework/gemini-agent",
|
|
725
|
-
description: "Gemini AI agent plugin",
|
|
726
|
-
kind: "headless",
|
|
727
|
-
requires: [],
|
|
728
|
-
showInAddPagePicker: true,
|
|
729
|
-
configFiles: ["config/.gemini-agent.yaml"]
|
|
730
|
-
},
|
|
731
|
-
{
|
|
732
|
-
pluginName: "webmcp",
|
|
733
|
-
packageName: "@jay-framework/webmcp",
|
|
734
|
-
description: "Web MCP tooling",
|
|
735
|
-
kind: "tooling",
|
|
736
|
-
requires: [],
|
|
737
|
-
showInAddPagePicker: false
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
pluginName: "aiditor",
|
|
741
|
-
packageName: "@jay-framework/aiditor",
|
|
742
|
-
description: "AIditor self plugin",
|
|
743
|
-
kind: "tooling",
|
|
744
|
-
requires: [],
|
|
745
|
-
showInAddPagePicker: false
|
|
746
|
-
}
|
|
747
|
-
];
|
|
748
|
-
function getJayPluginCatalog() {
|
|
749
|
-
return CATALOG.map((e) => ({ ...e, requires: [...e.requires] }));
|
|
750
|
-
}
|
|
751
|
-
function getCatalogEntry(pluginName) {
|
|
752
|
-
return CATALOG.find((e) => e.pluginName === pluginName);
|
|
753
|
-
}
|
|
754
|
-
const DEFAULT_COMPONENT_KEYS = {
|
|
755
|
-
"product-page": "p",
|
|
756
|
-
"product-search": "search",
|
|
757
|
-
"cart-indicator": "cart",
|
|
758
|
-
"cart-page": "cartPage",
|
|
759
|
-
"category-list": "categories"
|
|
760
|
-
};
|
|
761
|
-
function getDefaultComponentKey(contractName) {
|
|
762
|
-
if (contractName in DEFAULT_COMPONENT_KEYS) {
|
|
763
|
-
return DEFAULT_COMPONENT_KEYS[contractName];
|
|
1180
|
+
async function* streamAgentQuery(opts) {
|
|
1181
|
+
for await (const message of query({
|
|
1182
|
+
prompt: promptContent,
|
|
1183
|
+
options: {
|
|
1184
|
+
...sharedOptions,
|
|
1185
|
+
...opts
|
|
1186
|
+
}
|
|
1187
|
+
})) {
|
|
1188
|
+
for (const chunk of transformSDKMessage(message)) {
|
|
1189
|
+
yield chunk;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
let didStaleResumeRetry = false;
|
|
1194
|
+
try {
|
|
1195
|
+
if (sessionPlan.useResume) {
|
|
1196
|
+
yield* streamAgentQuery({ resume: sessionPlan.sessionId });
|
|
1197
|
+
} else {
|
|
1198
|
+
yield* streamAgentQuery({
|
|
1199
|
+
sessionId: sessionPlan.sessionId,
|
|
1200
|
+
title: routeKey
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
} catch (err) {
|
|
1204
|
+
if (sessionPlan.useResume && !didStaleResumeRetry) {
|
|
1205
|
+
didStaleResumeRetry = true;
|
|
1206
|
+
const fresh = await replaceRouteSessionAfterStaleResume(
|
|
1207
|
+
projectDir,
|
|
1208
|
+
routeKey
|
|
1209
|
+
);
|
|
1210
|
+
try {
|
|
1211
|
+
yield* streamAgentQuery({
|
|
1212
|
+
sessionId: fresh.sessionId,
|
|
1213
|
+
title: routeKey
|
|
1214
|
+
});
|
|
1215
|
+
} catch (err2) {
|
|
1216
|
+
yield {
|
|
1217
|
+
type: "error",
|
|
1218
|
+
message: err2 instanceof Error ? err2.message : String(err2)
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
} else {
|
|
1222
|
+
yield {
|
|
1223
|
+
type: "error",
|
|
1224
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
} finally {
|
|
1229
|
+
endRouteQuery(routeKey);
|
|
764
1230
|
}
|
|
765
|
-
|
|
766
|
-
}
|
|
1231
|
+
yield { type: "done" };
|
|
1232
|
+
});
|
|
767
1233
|
class AddPagePromptLoadError extends Error {
|
|
768
1234
|
constructor(message) {
|
|
769
1235
|
super(message);
|
|
@@ -814,34 +1280,6 @@ async function listDirFiles(dir, prefix) {
|
|
|
814
1280
|
return [];
|
|
815
1281
|
}
|
|
816
1282
|
}
|
|
817
|
-
function buildPluginManifestPromptSection(selectedPlugins) {
|
|
818
|
-
const lines = [
|
|
819
|
-
"",
|
|
820
|
-
"## Page plugin manifest (authoritative)",
|
|
821
|
-
"",
|
|
822
|
-
"The user selected these headless components for this page. You MUST:",
|
|
823
|
-
'1. Add `<script type="application/jay-headless" plugin="..." contract="..." key="...">` for each entry',
|
|
824
|
-
"2. Bind UI using contract ViewState and refs per agent-kit designer guides",
|
|
825
|
-
"3. Run jay-stack validate",
|
|
826
|
-
"",
|
|
827
|
-
"| Plugin | Contract | Key | Notes |",
|
|
828
|
-
"| --- | --- | --- | --- |"
|
|
829
|
-
];
|
|
830
|
-
for (const plugin of selectedPlugins) {
|
|
831
|
-
for (const contract of plugin.contracts) {
|
|
832
|
-
const key = contract.componentKey ?? getDefaultComponentKey(contract.contractName);
|
|
833
|
-
lines.push(
|
|
834
|
-
`| ${plugin.packageName} | ${contract.contractName} | ${key} | |`
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
lines.push(
|
|
839
|
-
"",
|
|
840
|
-
"If Wix config is incomplete, implement layout and headless imports; live store data may be unavailable until config/.wix.yaml is filled.",
|
|
841
|
-
""
|
|
842
|
-
);
|
|
843
|
-
return lines.join("\n");
|
|
844
|
-
}
|
|
845
1283
|
async function buildAddPagePrompt(input) {
|
|
846
1284
|
const { system, skill } = await loadAddPageAgentPrompts();
|
|
847
1285
|
const contentPath = path.join(input.requestDir, "content.md");
|
|
@@ -870,7 +1308,13 @@ async function buildAddPagePrompt(input) {
|
|
|
870
1308
|
## Retry mode
|
|
871
1309
|
The page at route \`${input.pageRoute}\` may already have partial files from a previous run. Read existing page files and **fix** validate errors rather than recreating from scratch.
|
|
872
1310
|
` : "";
|
|
873
|
-
const
|
|
1311
|
+
const attachments = resolveAddMenuAttachments(input);
|
|
1312
|
+
const catalog = await listAddMenuItems(input.projectDir);
|
|
1313
|
+
const itemById = new Map(catalog.items.map((item) => [item.id, item]));
|
|
1314
|
+
const addMenuSection = attachments.length > 0 ? buildAddMenuAttachmentPromptSection(attachments, itemById) : input.selectedPlugins?.length ? buildLegacyPluginManifestPromptSection(input.selectedPlugins) : "";
|
|
1315
|
+
const routeMigrationSection = buildRouteMigrationPromptSection(
|
|
1316
|
+
input.routeMigration
|
|
1317
|
+
);
|
|
874
1318
|
const userMessage = `# Add Page task
|
|
875
1319
|
|
|
876
1320
|
Create **one** Jay Stack page at route: \`${input.pageRoute}\` (${input.routeKind}).
|
|
@@ -898,7 +1342,7 @@ Asset files live under the request folder. **Copy** cited assets from \`${input.
|
|
|
898
1342
|
- Match user intent from content.md and design.md as closely as possible.
|
|
899
1343
|
- Reuse existing site patterns (header, footer, styling) from other pages when design.md requests it.
|
|
900
1344
|
- Summarize files created and how they cover the brief when done.
|
|
901
|
-
${
|
|
1345
|
+
${addMenuSection}${routeMigrationSection}
|
|
902
1346
|
---
|
|
903
1347
|
|
|
904
1348
|
${skill}`;
|
|
@@ -2062,7 +2506,9 @@ const submitAddPageAction = makeJayStream("aiditor.submitAddPage").withServices(
|
|
|
2062
2506
|
pageRoute: normalizedRoute,
|
|
2063
2507
|
routeKind,
|
|
2064
2508
|
isRetry: input.isRetry,
|
|
2065
|
-
selectedPlugins: request.selectedPlugins
|
|
2509
|
+
selectedPlugins: request.selectedPlugins,
|
|
2510
|
+
addMenuAttachments: request.addMenuAttachments,
|
|
2511
|
+
routeMigration: request.routeMigration
|
|
2066
2512
|
})) {
|
|
2067
2513
|
yield chunk;
|
|
2068
2514
|
if (chunk.type === "error") {
|
|
@@ -2090,227 +2536,103 @@ const submitAddPageAction = makeJayStream("aiditor.submitAddPage").withServices(
|
|
|
2090
2536
|
message: "Validation failed.",
|
|
2091
2537
|
text: validateResult.errors.join("\n"),
|
|
2092
2538
|
validateErrors: validateResult.errors
|
|
2093
|
-
};
|
|
2094
|
-
yield { type: "done" };
|
|
2095
|
-
return;
|
|
2096
|
-
}
|
|
2097
|
-
await updatePageRequestStatus(projectDir, input.requestId, "completed");
|
|
2098
|
-
await devServer.refreshRoutes();
|
|
2099
|
-
const jayHtmlPath = await resolveJayHtmlPathForPageRoute(
|
|
2100
|
-
projectDir,
|
|
2101
|
-
normalizedRoute,
|
|
2102
|
-
devServer.listRoutes()
|
|
2103
|
-
);
|
|
2104
|
-
if (jayHtmlPath) {
|
|
2105
|
-
try {
|
|
2106
|
-
await savePageBriefFromRequest(projectDir, jayHtmlPath, {
|
|
2107
|
-
requestId: input.requestId,
|
|
2108
|
-
pageRoute: normalizedRoute,
|
|
2109
|
-
contentMd: input.contentMd,
|
|
2110
|
-
designMd: input.designMd
|
|
2111
|
-
});
|
|
2112
|
-
} catch (err) {
|
|
2113
|
-
getLogger().warn(
|
|
2114
|
-
`[Add Page] Failed to save page brief for ${normalizedRoute}: ${err instanceof Error ? err.message : String(err)}`
|
|
2115
|
-
);
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
yield {
|
|
2119
|
-
type: "result",
|
|
2120
|
-
message: `Page created at ${normalizedRoute}.`,
|
|
2121
|
-
text: normalizedRoute
|
|
2122
|
-
};
|
|
2123
|
-
yield { type: "done" };
|
|
2124
|
-
});
|
|
2125
|
-
const generateAddPageBriefFromImageAction = makeJayQuery(
|
|
2126
|
-
"aiditor.generateAddPageBriefFromImage"
|
|
2127
|
-
).withMethod("POST").withFiles().withHandler(
|
|
2128
|
-
async (input) => {
|
|
2129
|
-
const imageFiles = extractImageFilesFromExtraFiles(input.extraFiles);
|
|
2130
|
-
const validationError = validateBriefFillImages(imageFiles);
|
|
2131
|
-
if (validationError) {
|
|
2132
|
-
return { ok: false, error: validationError };
|
|
2133
|
-
}
|
|
2134
|
-
const projectDir = process.cwd();
|
|
2135
|
-
if (input.requestId) {
|
|
2136
|
-
const request = await readPageRequest(projectDir, input.requestId);
|
|
2137
|
-
if (!request) {
|
|
2138
|
-
return {
|
|
2139
|
-
ok: false,
|
|
2140
|
-
error: `Page request not found: ${input.requestId}`
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
try {
|
|
2145
|
-
const images = imageFiles.map(readImageAsBase64);
|
|
2146
|
-
const result = await generateBriefFromImages(
|
|
2147
|
-
{
|
|
2148
|
-
target: input.target,
|
|
2149
|
-
images,
|
|
2150
|
-
contextNotes: input.contextNotes,
|
|
2151
|
-
pageRoute: input.pageRoute
|
|
2152
|
-
},
|
|
2153
|
-
{ projectDir }
|
|
2154
|
-
);
|
|
2155
|
-
const referenceAttachments = [];
|
|
2156
|
-
if (input.requestId) {
|
|
2157
|
-
for (const file of imageFiles) {
|
|
2158
|
-
const id = generatePageRequestId();
|
|
2159
|
-
const filename = sanitizeAttachmentFilename(file.name);
|
|
2160
|
-
const relPath = `references/${filename}`;
|
|
2161
|
-
const destPath = path.join(
|
|
2162
|
-
getPageRequestDir(projectDir, input.requestId),
|
|
2163
|
-
relPath
|
|
2164
|
-
);
|
|
2165
|
-
persistUpload(file, destPath);
|
|
2166
|
-
await addPageRequestAttachment(
|
|
2167
|
-
projectDir,
|
|
2168
|
-
input.requestId,
|
|
2169
|
-
{ id, path: relPath, label: filename },
|
|
2170
|
-
"reference"
|
|
2171
|
-
);
|
|
2172
|
-
referenceAttachments.push({ id, path: relPath, filename });
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
return {
|
|
2176
|
-
ok: true,
|
|
2177
|
-
markdown: result.markdown,
|
|
2178
|
-
suggestedRoute: result.suggestedRoute,
|
|
2179
|
-
suggestedRouteKind: result.suggestedRouteKind,
|
|
2180
|
-
referenceAttachments
|
|
2181
|
-
};
|
|
2182
|
-
} catch (err) {
|
|
2183
|
-
return {
|
|
2184
|
-
ok: false,
|
|
2185
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2186
|
-
};
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
);
|
|
2190
|
-
function parsePluginsIndexYaml(yaml) {
|
|
2191
|
-
const entries = [];
|
|
2192
|
-
let currentPlugin = null;
|
|
2193
|
-
let inContracts = false;
|
|
2194
|
-
for (const line of yaml.split("\n")) {
|
|
2195
|
-
const pluginMatch = line.match(/^ - name:\s*(.+)$/);
|
|
2196
|
-
if (pluginMatch && !line.startsWith(" ")) {
|
|
2197
|
-
currentPlugin = pluginMatch[1].trim();
|
|
2198
|
-
inContracts = false;
|
|
2199
|
-
continue;
|
|
2200
|
-
}
|
|
2201
|
-
if (line.trim() === "contracts:") {
|
|
2202
|
-
inContracts = true;
|
|
2203
|
-
continue;
|
|
2204
|
-
}
|
|
2205
|
-
if (inContracts && currentPlugin) {
|
|
2206
|
-
const contractMatch = line.match(/^ - name:\s*(.+)$/);
|
|
2207
|
-
if (contractMatch) {
|
|
2208
|
-
entries.push({
|
|
2209
|
-
pluginName: currentPlugin,
|
|
2210
|
-
contractName: contractMatch[1].trim()
|
|
2211
|
-
});
|
|
2212
|
-
continue;
|
|
2213
|
-
}
|
|
2214
|
-
const descMatch = line.match(/^ description:\s*(.+)$/);
|
|
2215
|
-
if (descMatch && entries.length > 0) {
|
|
2216
|
-
const last = entries[entries.length - 1];
|
|
2217
|
-
if (last.pluginName === currentPlugin) {
|
|
2218
|
-
last.description = descMatch[1].trim();
|
|
2219
|
-
}
|
|
2220
|
-
continue;
|
|
2221
|
-
}
|
|
2222
|
-
const typeMatch = line.match(/^ type:\s*(.+)$/);
|
|
2223
|
-
if (typeMatch && entries.length > 0) {
|
|
2224
|
-
const last = entries[entries.length - 1];
|
|
2225
|
-
if (last.pluginName === currentPlugin) {
|
|
2226
|
-
last.isDynamic = typeMatch[1].trim() === "dynamic";
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
if (line.match(/^ actions:/) || line.match(/^ routes:/)) {
|
|
2231
|
-
inContracts = false;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
return entries;
|
|
2235
|
-
}
|
|
2236
|
-
function packageNameToPluginName(packageName) {
|
|
2237
|
-
const prefix = "@jay-framework/";
|
|
2238
|
-
if (packageName.startsWith(prefix)) {
|
|
2239
|
-
return packageName.slice(prefix.length);
|
|
2240
|
-
}
|
|
2241
|
-
return packageName;
|
|
2242
|
-
}
|
|
2243
|
-
async function readPackageJsonDeps(projectRoot) {
|
|
2244
|
-
const raw = await fs.readFile(
|
|
2245
|
-
path.join(projectRoot, "package.json"),
|
|
2246
|
-
"utf-8"
|
|
2247
|
-
);
|
|
2248
|
-
const pkg = JSON.parse(raw);
|
|
2249
|
-
return { ...pkg.devDependencies, ...pkg.dependencies };
|
|
2250
|
-
}
|
|
2251
|
-
async function getInstalledPlugins(projectRoot) {
|
|
2252
|
-
const deps = await readPackageJsonDeps(projectRoot);
|
|
2253
|
-
const installed = [];
|
|
2254
|
-
for (const [packageName, spec] of Object.entries(deps)) {
|
|
2255
|
-
if (!packageName.startsWith("@jay-framework/")) continue;
|
|
2256
|
-
installed.push({
|
|
2257
|
-
pluginName: packageNameToPluginName(packageName),
|
|
2258
|
-
packageName,
|
|
2259
|
-
installSpec: spec
|
|
2260
|
-
});
|
|
2261
|
-
}
|
|
2262
|
-
return installed;
|
|
2263
|
-
}
|
|
2264
|
-
async function isPluginInstalled(projectRoot, pluginName) {
|
|
2265
|
-
const installed = await getInstalledPlugins(projectRoot);
|
|
2266
|
-
return installed.some((p) => p.pluginName === pluginName);
|
|
2267
|
-
}
|
|
2268
|
-
async function parsePluginsIndexContracts(projectRoot) {
|
|
2269
|
-
const indexPath = path.join(projectRoot, "agent-kit", "plugins-index.yaml");
|
|
2270
|
-
try {
|
|
2271
|
-
const yaml = await fs.readFile(indexPath, "utf-8");
|
|
2272
|
-
return parsePluginsIndexYaml(yaml);
|
|
2273
|
-
} catch (e) {
|
|
2274
|
-
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
2275
|
-
if (code === "ENOENT") return [];
|
|
2276
|
-
throw e;
|
|
2539
|
+
};
|
|
2540
|
+
yield { type: "done" };
|
|
2541
|
+
return;
|
|
2277
2542
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
pluginName,
|
|
2285
|
-
"plugin.yaml"
|
|
2543
|
+
await updatePageRequestStatus(projectDir, input.requestId, "completed");
|
|
2544
|
+
await devServer.refreshRoutes();
|
|
2545
|
+
const jayHtmlPath = await resolveJayHtmlPathForPageRoute(
|
|
2546
|
+
projectDir,
|
|
2547
|
+
normalizedRoute,
|
|
2548
|
+
devServer.listRoutes()
|
|
2286
2549
|
);
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2550
|
+
if (jayHtmlPath) {
|
|
2551
|
+
try {
|
|
2552
|
+
await savePageBriefFromRequest(projectDir, jayHtmlPath, {
|
|
2553
|
+
requestId: input.requestId,
|
|
2554
|
+
pageRoute: normalizedRoute,
|
|
2555
|
+
contentMd: input.contentMd,
|
|
2556
|
+
designMd: input.designMd
|
|
2557
|
+
});
|
|
2558
|
+
} catch (err) {
|
|
2559
|
+
getLogger().warn(
|
|
2560
|
+
`[Add Page] Failed to save page brief for ${normalizedRoute}: ${err instanceof Error ? err.message : String(err)}`
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
yield {
|
|
2565
|
+
type: "result",
|
|
2566
|
+
message: `Page created at ${normalizedRoute}.`,
|
|
2567
|
+
text: normalizedRoute
|
|
2568
|
+
};
|
|
2569
|
+
yield { type: "done" };
|
|
2570
|
+
});
|
|
2571
|
+
const generateAddPageBriefFromImageAction = makeJayQuery(
|
|
2572
|
+
"aiditor.generateAddPageBriefFromImage"
|
|
2573
|
+
).withMethod("POST").withFiles().withHandler(
|
|
2574
|
+
async (input) => {
|
|
2575
|
+
const imageFiles = extractImageFilesFromExtraFiles(input.extraFiles);
|
|
2576
|
+
const validationError = validateBriefFillImages(imageFiles);
|
|
2577
|
+
if (validationError) {
|
|
2578
|
+
return { ok: false, error: validationError };
|
|
2579
|
+
}
|
|
2580
|
+
const projectDir = process.cwd();
|
|
2581
|
+
if (input.requestId) {
|
|
2582
|
+
const request = await readPageRequest(projectDir, input.requestId);
|
|
2583
|
+
if (!request) {
|
|
2584
|
+
return {
|
|
2585
|
+
ok: false,
|
|
2586
|
+
error: `Page request not found: ${input.requestId}`
|
|
2587
|
+
};
|
|
2295
2588
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2589
|
+
}
|
|
2590
|
+
try {
|
|
2591
|
+
const images = imageFiles.map(readImageAsBase64);
|
|
2592
|
+
const result = await generateBriefFromImages(
|
|
2593
|
+
{
|
|
2594
|
+
target: input.target,
|
|
2595
|
+
images,
|
|
2596
|
+
contextNotes: input.contextNotes,
|
|
2597
|
+
pageRoute: input.pageRoute
|
|
2598
|
+
},
|
|
2599
|
+
{ projectDir }
|
|
2600
|
+
);
|
|
2601
|
+
const referenceAttachments = [];
|
|
2602
|
+
if (input.requestId) {
|
|
2603
|
+
for (const file of imageFiles) {
|
|
2604
|
+
const id = generatePageRequestId();
|
|
2605
|
+
const filename = sanitizeAttachmentFilename(file.name);
|
|
2606
|
+
const relPath = `references/${filename}`;
|
|
2607
|
+
const destPath = path.join(
|
|
2608
|
+
getPageRequestDir(projectDir, input.requestId),
|
|
2609
|
+
relPath
|
|
2610
|
+
);
|
|
2611
|
+
persistUpload(file, destPath);
|
|
2612
|
+
await addPageRequestAttachment(
|
|
2613
|
+
projectDir,
|
|
2614
|
+
input.requestId,
|
|
2615
|
+
{ id, path: relPath, label: filename },
|
|
2616
|
+
"reference"
|
|
2617
|
+
);
|
|
2618
|
+
referenceAttachments.push({ id, path: relPath, filename });
|
|
2306
2619
|
}
|
|
2307
2620
|
}
|
|
2621
|
+
return {
|
|
2622
|
+
ok: true,
|
|
2623
|
+
markdown: result.markdown,
|
|
2624
|
+
suggestedRoute: result.suggestedRoute,
|
|
2625
|
+
suggestedRouteKind: result.suggestedRouteKind,
|
|
2626
|
+
referenceAttachments
|
|
2627
|
+
};
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
return {
|
|
2630
|
+
ok: false,
|
|
2631
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2632
|
+
};
|
|
2308
2633
|
}
|
|
2309
|
-
return entries;
|
|
2310
|
-
} catch {
|
|
2311
|
-
return [];
|
|
2312
2634
|
}
|
|
2313
|
-
|
|
2635
|
+
);
|
|
2314
2636
|
const LOCAL_SPEC_PREFIXES = ["portal:", "file:", "workspace:"];
|
|
2315
2637
|
function isRegistryInstallSpec(installSpec) {
|
|
2316
2638
|
if (!installSpec) return true;
|
|
@@ -2721,9 +3043,35 @@ async function checkPluginUpdate(projectRoot, pluginName, packageName, installSp
|
|
|
2721
3043
|
message: updateAvailable ? `Update available: ${installedVersion} → ${latestVersion}` : installedVersion ? `Up to date (${installedVersion})` : void 0
|
|
2722
3044
|
};
|
|
2723
3045
|
}
|
|
3046
|
+
const writeQueues = /* @__PURE__ */ new Map();
|
|
2724
3047
|
function getSetupCachePath(projectRoot) {
|
|
2725
3048
|
return path.join(projectRoot, ".aiditor", "plugin-setup-status.json");
|
|
2726
3049
|
}
|
|
3050
|
+
function getSetupCacheTempPath(projectRoot) {
|
|
3051
|
+
return `${getSetupCachePath(projectRoot)}.tmp`;
|
|
3052
|
+
}
|
|
3053
|
+
function enqueueCacheWrite(projectRoot, operation) {
|
|
3054
|
+
const previous = writeQueues.get(projectRoot) ?? Promise.resolve();
|
|
3055
|
+
const next = previous.catch(() => void 0).then(operation);
|
|
3056
|
+
writeQueues.set(
|
|
3057
|
+
projectRoot,
|
|
3058
|
+
next.then(
|
|
3059
|
+
() => void 0,
|
|
3060
|
+
() => void 0
|
|
3061
|
+
)
|
|
3062
|
+
);
|
|
3063
|
+
return next;
|
|
3064
|
+
}
|
|
3065
|
+
async function writeSetupCacheFile(projectRoot, cache2) {
|
|
3066
|
+
const dir = path.join(projectRoot, ".aiditor");
|
|
3067
|
+
await fs.mkdir(dir, { recursive: true });
|
|
3068
|
+
const filePath = getSetupCachePath(projectRoot);
|
|
3069
|
+
const tempPath = getSetupCacheTempPath(projectRoot);
|
|
3070
|
+
const body = `${JSON.stringify(cache2, null, 2)}
|
|
3071
|
+
`;
|
|
3072
|
+
await fs.writeFile(tempPath, body, "utf-8");
|
|
3073
|
+
await fs.rename(tempPath, filePath);
|
|
3074
|
+
}
|
|
2727
3075
|
async function readSetupCache(projectRoot) {
|
|
2728
3076
|
const filePath = getSetupCachePath(projectRoot);
|
|
2729
3077
|
try {
|
|
@@ -2732,20 +3080,20 @@ async function readSetupCache(projectRoot) {
|
|
|
2732
3080
|
} catch (e) {
|
|
2733
3081
|
const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
|
|
2734
3082
|
if (code === "ENOENT") return {};
|
|
3083
|
+
if (e instanceof SyntaxError) {
|
|
3084
|
+
const backupPath = `${filePath}.corrupt-${Date.now()}`;
|
|
3085
|
+
await fs.rename(filePath, backupPath).catch(() => void 0);
|
|
3086
|
+
return {};
|
|
3087
|
+
}
|
|
2735
3088
|
throw e;
|
|
2736
3089
|
}
|
|
2737
3090
|
}
|
|
2738
3091
|
async function writeSetupCacheEntry(projectRoot, pluginName, entry) {
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
getSetupCachePath(projectRoot),
|
|
2745
|
-
`${JSON.stringify(cache2, null, 2)}
|
|
2746
|
-
`,
|
|
2747
|
-
"utf-8"
|
|
2748
|
-
);
|
|
3092
|
+
return enqueueCacheWrite(projectRoot, async () => {
|
|
3093
|
+
const cache2 = await readSetupCache(projectRoot);
|
|
3094
|
+
cache2[pluginName] = entry;
|
|
3095
|
+
await writeSetupCacheFile(projectRoot, cache2);
|
|
3096
|
+
});
|
|
2749
3097
|
}
|
|
2750
3098
|
function getCachedSetupStatus(cache2, pluginName) {
|
|
2751
3099
|
return cache2[pluginName];
|
|
@@ -2906,57 +3254,133 @@ async function* ensureProjectPlugin(projectRoot, pluginName, options) {
|
|
|
2906
3254
|
message: parsed.message
|
|
2907
3255
|
};
|
|
2908
3256
|
}
|
|
2909
|
-
|
|
2910
|
-
const
|
|
2911
|
-
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
)
|
|
3257
|
+
function parseContractParamsFromYaml(contractYaml, contractName, pluginName) {
|
|
3258
|
+
const lines = contractYaml.split("\n");
|
|
3259
|
+
let inParams = false;
|
|
3260
|
+
const allParams = [];
|
|
3261
|
+
const requiredParams = [];
|
|
3262
|
+
for (const line of lines) {
|
|
3263
|
+
if (!inParams) {
|
|
3264
|
+
if (line.match(/^params:\s*$/)) {
|
|
3265
|
+
inParams = true;
|
|
3266
|
+
}
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
if (line.match(/^[a-zA-Z]/) && !line.startsWith(" ")) {
|
|
3270
|
+
break;
|
|
3271
|
+
}
|
|
3272
|
+
const paramMatch = line.match(/^\s{2}([a-zA-Z0-9_]+):\s*(.+)$/);
|
|
3273
|
+
if (paramMatch) {
|
|
3274
|
+
const name = paramMatch[1];
|
|
3275
|
+
const typePart = paramMatch[2].trim();
|
|
3276
|
+
allParams.push(name);
|
|
3277
|
+
const isOptional = typePart.endsWith("?") || line.match(/^\s{2}[a-zA-Z0-9_]+\?:/) !== null;
|
|
3278
|
+
if (!isOptional) {
|
|
3279
|
+
requiredParams.push(name);
|
|
3280
|
+
}
|
|
2931
3281
|
}
|
|
2932
3282
|
}
|
|
2933
|
-
|
|
2934
|
-
return
|
|
3283
|
+
if (allParams.length === 0) return null;
|
|
3284
|
+
return { contractName, pluginName, requiredParams, allParams };
|
|
2935
3285
|
}
|
|
2936
|
-
function
|
|
2937
|
-
const
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3286
|
+
function contractNameFromItemId(itemId) {
|
|
3287
|
+
const colon = itemId.indexOf(":");
|
|
3288
|
+
if (colon < 0 || colon === itemId.length - 1) return null;
|
|
3289
|
+
return itemId.slice(colon + 1);
|
|
3290
|
+
}
|
|
3291
|
+
function pluginNameFromItemId(itemId) {
|
|
3292
|
+
const colon = itemId.indexOf(":");
|
|
3293
|
+
if (colon <= 0) return null;
|
|
3294
|
+
return itemId.slice(0, colon);
|
|
3295
|
+
}
|
|
3296
|
+
async function resolveContractParamsForItem(projectRoot, item) {
|
|
3297
|
+
const pluginName = item.pluginName ?? pluginNameFromItemId(item.id);
|
|
3298
|
+
const contractName = contractNameFromItemId(item.id);
|
|
3299
|
+
if (!pluginName || !contractName) return null;
|
|
3300
|
+
const contractPath = path.join(
|
|
3301
|
+
projectRoot,
|
|
3302
|
+
"agent-kit",
|
|
3303
|
+
"materialized-contracts",
|
|
3304
|
+
pluginName,
|
|
3305
|
+
`${contractName}.jay-contract`
|
|
3306
|
+
);
|
|
3307
|
+
let yaml;
|
|
3308
|
+
try {
|
|
3309
|
+
yaml = await fs.readFile(contractPath, "utf-8");
|
|
3310
|
+
} catch {
|
|
3311
|
+
return null;
|
|
2947
3312
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
3313
|
+
return parseContractParamsFromYaml(yaml, contractName, pluginName);
|
|
3314
|
+
}
|
|
3315
|
+
const listAddMenuItemsAction = makeJayQuery("aiditor.listAddMenuItems").withServices(DEV_SERVER_SERVICE).withHandler(async () => {
|
|
3316
|
+
const projectRoot = process.cwd();
|
|
3317
|
+
return listAddMenuItems(projectRoot);
|
|
3318
|
+
});
|
|
3319
|
+
const resolveAddMenuContractParamsAction = makeJayQuery(
|
|
3320
|
+
"aiditor.resolveAddMenuContractParams"
|
|
3321
|
+
).withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
|
|
3322
|
+
const projectRoot = process.cwd();
|
|
3323
|
+
const resolved = await resolveContractParamsForItem(projectRoot, {
|
|
3324
|
+
id: input.itemId,
|
|
3325
|
+
pluginName: input.pluginName
|
|
3326
|
+
});
|
|
3327
|
+
if (!resolved) {
|
|
3328
|
+
return {
|
|
3329
|
+
contractName: null,
|
|
3330
|
+
pluginName: null,
|
|
3331
|
+
requiredParams: null,
|
|
3332
|
+
allParams: null
|
|
3333
|
+
};
|
|
2954
3334
|
}
|
|
2955
|
-
return
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3335
|
+
return {
|
|
3336
|
+
contractName: resolved.contractName,
|
|
3337
|
+
pluginName: resolved.pluginName,
|
|
3338
|
+
requiredParams: resolved.requiredParams,
|
|
3339
|
+
allParams: resolved.allParams
|
|
3340
|
+
};
|
|
3341
|
+
});
|
|
3342
|
+
async function syncAddMenuAttachmentsCore(projectRoot, input) {
|
|
3343
|
+
const request = await readPageRequest(projectRoot, input.requestId);
|
|
3344
|
+
if (!request) {
|
|
3345
|
+
return { ok: false, contentMd: "", error: "Page request not found." };
|
|
3346
|
+
}
|
|
3347
|
+
const requestDir = path.join(
|
|
3348
|
+
projectRoot,
|
|
3349
|
+
".aiditor",
|
|
3350
|
+
"page-requests",
|
|
3351
|
+
input.requestId
|
|
3352
|
+
);
|
|
3353
|
+
const contentPath = path.join(requestDir, "content.md");
|
|
3354
|
+
let contentMd = "";
|
|
3355
|
+
if (input.contentMd !== void 0) {
|
|
3356
|
+
contentMd = input.contentMd;
|
|
3357
|
+
await writePageRequestMarkdown(projectRoot, input.requestId, {
|
|
3358
|
+
contentMd,
|
|
3359
|
+
designMd: await fs.readFile(path.join(requestDir, "design.md"), "utf-8").catch(() => "")
|
|
3360
|
+
});
|
|
3361
|
+
} else {
|
|
3362
|
+
try {
|
|
3363
|
+
contentMd = await fs.readFile(contentPath, "utf-8");
|
|
3364
|
+
} catch {
|
|
3365
|
+
contentMd = "";
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
await writePageRequest(projectRoot, {
|
|
3369
|
+
...request,
|
|
3370
|
+
addMenuAttachments: input.addMenuAttachments,
|
|
3371
|
+
routeMigration: input.routeMigration,
|
|
3372
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3373
|
+
});
|
|
3374
|
+
return { ok: true, contentMd };
|
|
2959
3375
|
}
|
|
3376
|
+
const syncAddMenuAttachmentsAction = makeJayQuery(
|
|
3377
|
+
"aiditor.syncAddMenuAttachments"
|
|
3378
|
+
).withServices(DEV_SERVER_SERVICE).withHandler(
|
|
3379
|
+
async (input) => {
|
|
3380
|
+
const projectRoot = process.cwd();
|
|
3381
|
+
return syncAddMenuAttachmentsCore(projectRoot, input);
|
|
3382
|
+
}
|
|
3383
|
+
);
|
|
2960
3384
|
async function contractsForPlugin(projectRoot, pluginName) {
|
|
2961
3385
|
const fromIndex = await parsePluginsIndexContracts(projectRoot);
|
|
2962
3386
|
const filtered = fromIndex.filter((c) => c.pluginName === pluginName);
|
|
@@ -3205,37 +3629,12 @@ const syncAddPagePluginManifestAction = makeJayQuery(
|
|
|
3205
3629
|
).withServices(DEV_SERVER_SERVICE).withHandler(
|
|
3206
3630
|
async (input) => {
|
|
3207
3631
|
const projectRoot = process.cwd();
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
projectRoot,
|
|
3214
|
-
".aiditor",
|
|
3215
|
-
"page-requests",
|
|
3216
|
-
input.requestId
|
|
3217
|
-
);
|
|
3218
|
-
const contentPath = path.join(requestDir, "content.md");
|
|
3219
|
-
let contentMd = "";
|
|
3220
|
-
try {
|
|
3221
|
-
contentMd = await fs.readFile(contentPath, "utf-8");
|
|
3222
|
-
} catch {
|
|
3223
|
-
contentMd = "";
|
|
3224
|
-
}
|
|
3225
|
-
const nextMd = syncPluginManifestInContentMd(
|
|
3226
|
-
contentMd,
|
|
3227
|
-
input.selectedPlugins
|
|
3228
|
-
);
|
|
3229
|
-
await writePageRequestMarkdown(projectRoot, input.requestId, {
|
|
3230
|
-
contentMd: nextMd,
|
|
3231
|
-
designMd: await fs.readFile(path.join(requestDir, "design.md"), "utf-8").catch(() => "")
|
|
3232
|
-
});
|
|
3233
|
-
await writePageRequest(projectRoot, {
|
|
3234
|
-
...request,
|
|
3235
|
-
selectedPlugins: input.selectedPlugins,
|
|
3236
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3632
|
+
return syncAddMenuAttachmentsCore(projectRoot, {
|
|
3633
|
+
requestId: input.requestId,
|
|
3634
|
+
addMenuAttachments: pagePluginSelectionsToAttachments(
|
|
3635
|
+
input.selectedPlugins
|
|
3636
|
+
)
|
|
3237
3637
|
});
|
|
3238
|
-
return { ok: true, contentMd: nextMd };
|
|
3239
3638
|
}
|
|
3240
3639
|
);
|
|
3241
3640
|
const writePluginSourceAction = makeJayQuery("aiditor.writePluginSource").withServices(DEV_SERVER_SERVICE).withHandler(async (input) => {
|
|
@@ -3243,8 +3642,81 @@ const writePluginSourceAction = makeJayQuery("aiditor.writePluginSource").withSe
|
|
|
3243
3642
|
await writePluginSource(projectRoot, input);
|
|
3244
3643
|
return { ok: true };
|
|
3245
3644
|
});
|
|
3246
|
-
|
|
3247
|
-
const
|
|
3645
|
+
const AIDITOR_ADD_MENU_DOC = "aiditor-add-menu.md";
|
|
3646
|
+
const INSTRUCTIONS_LINK_LINE = "- [Add Menu integration](./aiditor-add-menu.md) — contribute catalog items for AIditor";
|
|
3647
|
+
async function resolveTemplateDir(fromModuleUrl) {
|
|
3648
|
+
const thisDir = path.dirname(fileURLToPath(fromModuleUrl));
|
|
3649
|
+
const candidates = [
|
|
3650
|
+
path.join(thisDir, "agent-kit-template", "plugin"),
|
|
3651
|
+
path.join(thisDir, "..", "agent-kit-template", "plugin"),
|
|
3652
|
+
path.join(thisDir, "..", "..", "agent-kit-template", "plugin")
|
|
3653
|
+
];
|
|
3654
|
+
for (const dir of candidates) {
|
|
3655
|
+
const probe = path.join(dir, AIDITOR_ADD_MENU_DOC);
|
|
3656
|
+
try {
|
|
3657
|
+
await fs.access(probe);
|
|
3658
|
+
return dir;
|
|
3659
|
+
} catch {
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
throw new Error(
|
|
3663
|
+
`aiditor add-menu template not found (searched from ${thisDir})`
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
async function appendInstructionsLink(instructionsPath, force) {
|
|
3667
|
+
let content;
|
|
3668
|
+
try {
|
|
3669
|
+
content = await fs.readFile(instructionsPath, "utf-8");
|
|
3670
|
+
} catch {
|
|
3671
|
+
return false;
|
|
3672
|
+
}
|
|
3673
|
+
if (content.includes(AIDITOR_ADD_MENU_DOC) && !force) {
|
|
3674
|
+
return false;
|
|
3675
|
+
}
|
|
3676
|
+
if (content.includes(AIDITOR_ADD_MENU_DOC) && force) {
|
|
3677
|
+
return false;
|
|
3678
|
+
}
|
|
3679
|
+
const next = content.trimEnd() + `
|
|
3680
|
+
|
|
3681
|
+
## Add Menu
|
|
3682
|
+
|
|
3683
|
+
${INSTRUCTIONS_LINK_LINE}
|
|
3684
|
+
`;
|
|
3685
|
+
await fs.writeFile(instructionsPath, next, "utf-8");
|
|
3686
|
+
return true;
|
|
3687
|
+
}
|
|
3688
|
+
async function setupAiditor(ctx) {
|
|
3689
|
+
const templateDir = await resolveTemplateDir(import.meta.url);
|
|
3690
|
+
const srcDoc = path.join(templateDir, AIDITOR_ADD_MENU_DOC);
|
|
3691
|
+
const agentKitPluginDir = path.join(ctx.projectRoot, "agent-kit", "plugin");
|
|
3692
|
+
const destDoc = path.join(agentKitPluginDir, AIDITOR_ADD_MENU_DOC);
|
|
3693
|
+
await fs.mkdir(agentKitPluginDir, { recursive: true });
|
|
3694
|
+
const filesCreated = [];
|
|
3695
|
+
const filesUpdated = [];
|
|
3696
|
+
let exists = false;
|
|
3697
|
+
try {
|
|
3698
|
+
await fs.access(destDoc);
|
|
3699
|
+
exists = true;
|
|
3700
|
+
} catch {
|
|
3701
|
+
exists = false;
|
|
3702
|
+
}
|
|
3703
|
+
if (!exists || ctx.force) {
|
|
3704
|
+
await fs.copyFile(srcDoc, destDoc);
|
|
3705
|
+
if (exists) filesUpdated.push(`agent-kit/plugin/${AIDITOR_ADD_MENU_DOC}`);
|
|
3706
|
+
else filesCreated.push(`agent-kit/plugin/${AIDITOR_ADD_MENU_DOC}`);
|
|
3707
|
+
}
|
|
3708
|
+
const instructionsPath = path.join(agentKitPluginDir, "INSTRUCTIONS.md");
|
|
3709
|
+
const linked = await appendInstructionsLink(instructionsPath, ctx.force);
|
|
3710
|
+
if (linked) {
|
|
3711
|
+
filesUpdated.push("agent-kit/plugin/INSTRUCTIONS.md");
|
|
3712
|
+
}
|
|
3713
|
+
return {
|
|
3714
|
+
status: "configured",
|
|
3715
|
+
message: "AIditor Add Menu contributor guide installed.",
|
|
3716
|
+
...filesCreated.length > 0 ? { filesCreated } : {},
|
|
3717
|
+
...filesUpdated.length > 0 ? { filesUpdated } : {}
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3248
3720
|
const aiditorShell = makeJayStackComponent().withProps().withSlowlyRender(async () => {
|
|
3249
3721
|
return phaseOutput({}, { headline: "AIditor" });
|
|
3250
3722
|
}).withFastRender(async (_props, carryForward) => {
|
|
@@ -3256,6 +3728,8 @@ const aiditorShell = makeJayStackComponent().withProps().withSlowlyRender(async
|
|
|
3256
3728
|
carryForward
|
|
3257
3729
|
}));
|
|
3258
3730
|
});
|
|
3731
|
+
setActionCallerOptions({ timeout: 12e4 });
|
|
3732
|
+
const page = makeJayStackComponent().withProps();
|
|
3259
3733
|
export {
|
|
3260
3734
|
page as aiditorPage,
|
|
3261
3735
|
aiditorShell,
|
|
@@ -3268,6 +3742,7 @@ export {
|
|
|
3268
3742
|
getPageParamsAction,
|
|
3269
3743
|
getPluginSetupStatusAction,
|
|
3270
3744
|
getProjectInfoAction,
|
|
3745
|
+
listAddMenuItemsAction,
|
|
3271
3746
|
listFreezesAction,
|
|
3272
3747
|
listJayPluginsAction,
|
|
3273
3748
|
listPluginContractsAction,
|
|
@@ -3275,10 +3750,13 @@ export {
|
|
|
3275
3750
|
readFileAction,
|
|
3276
3751
|
reclassifyAddPageAssetAction,
|
|
3277
3752
|
rerunPluginSetupAction,
|
|
3753
|
+
resolveAddMenuContractParamsAction,
|
|
3278
3754
|
saveAddPageDraftAction,
|
|
3755
|
+
setupAiditor,
|
|
3279
3756
|
startAddPageRequestAction,
|
|
3280
3757
|
submitAddPageAction,
|
|
3281
3758
|
submitTaskAction,
|
|
3759
|
+
syncAddMenuAttachmentsAction,
|
|
3282
3760
|
syncAddPagePluginManifestAction,
|
|
3283
3761
|
uploadAddPageAssetAction,
|
|
3284
3762
|
writePluginSourceAction
|