@jay-framework/aiditor 0.20.0 → 0.22.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/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as _jay_framework_dev_server from '@jay-framework/dev-server';
2
2
  import * as _jay_framework_fullstack_component from '@jay-framework/fullstack-component';
3
3
  import { JayFile } from '@jay-framework/fullstack-component';
4
+ import { AddMenuCatalogLintWarning, AddMenuItem, AddMenuPresentation, AddMenuBrowseSize } from '@jay-framework/plugin-validator';
5
+ export { AddMenuItem, AddMenuPresentation } from '@jay-framework/plugin-validator';
4
6
  import { PluginSetupContext, PluginSetupResult } from '@jay-framework/stack-server-runtime';
5
7
  export { aiditorPage, aiditorShell } from './index.client.js';
6
8
  import '@jay-framework/component';
@@ -110,35 +112,24 @@ declare const submitTaskAction: _jay_framework_fullstack_component.JayStreamActi
110
112
  declare const cancelAgentTaskAction: _jay_framework_fullstack_component.JayAction<{
111
113
  pageRoute?: string;
112
114
  renderedUrl?: string;
115
+ cancelKey?: string;
113
116
  }, {
114
117
  cancelled: boolean;
115
118
  }> & _jay_framework_fullstack_component.JayActionDefinition<{
116
119
  pageRoute?: string;
117
120
  renderedUrl?: string;
121
+ cancelKey?: string;
118
122
  }, {
119
123
  cancelled: boolean;
120
124
  }, []>;
121
125
 
122
- /** Design Log #19 — Add Menu agreement types (M19.1). */
123
- type AddMenuInteraction = {
124
- mode: "reference" | "stage-place";
125
- persistOnPage?: boolean;
126
- stagePromptTemplate?: string;
127
- };
128
- type AddMenuItem = {
129
- /** Stable unique id, convention: `<pluginName>:<slug>` */
130
- id: string;
131
- title: string;
132
- /** Menu navigation label — not a type system */
133
- category: string;
134
- /** Injected verbatim into agent prompt when attached */
135
- prompt: string;
136
- pluginName?: string;
137
- packageName?: string;
138
- subCategory?: string;
139
- thumbnail?: string;
140
- interaction?: AddMenuInteraction;
141
- };
126
+ /**
127
+ * AIditor Add Menu runtime types.
128
+ * Catalog item schema types come from @jay-framework/plugin-validator (source of truth).
129
+ */
130
+
131
+ /** Runtime catalog warning — same shape as plugin-validator lint findings. */
132
+ type AddMenuCatalogWarning = AddMenuCatalogLintWarning;
142
133
  type AddMenuAttachment = {
143
134
  itemId: string;
144
135
  /** Change Request: set when chip is on a marker; omit = request-level */
@@ -161,7 +152,7 @@ type AddMenuSubCategoryGroup = {
161
152
  type ListAddMenuItemsResult = {
162
153
  items: AddMenuItem[];
163
154
  categories: AddMenuCategoryGroup[];
164
- warnings: string[];
155
+ warnings: AddMenuCatalogWarning[];
165
156
  };
166
157
 
167
158
  type PageBriefAttachment = {
@@ -384,9 +375,9 @@ declare const generateAddPageBriefFromImageAction: _jay_framework_fullstack_comp
384
375
  referenceAttachments?: undefined;
385
376
  } | {
386
377
  ok: boolean;
387
- markdown: string;
388
- suggestedRoute: string;
389
- suggestedRouteKind: "static" | "dynamic";
378
+ markdown: any;
379
+ suggestedRoute: any;
380
+ suggestedRouteKind: any;
390
381
  referenceAttachments: {
391
382
  id: string;
392
383
  path: string;
@@ -408,9 +399,9 @@ declare const generateAddPageBriefFromImageAction: _jay_framework_fullstack_comp
408
399
  referenceAttachments?: undefined;
409
400
  } | {
410
401
  ok: boolean;
411
- markdown: string;
412
- suggestedRoute: string;
413
- suggestedRouteKind: "static" | "dynamic";
402
+ markdown: any;
403
+ suggestedRoute: any;
404
+ suggestedRouteKind: any;
414
405
  referenceAttachments: {
415
406
  id: string;
416
407
  path: string;
@@ -763,7 +754,6 @@ type PageAssetEntry = {
763
754
  source: PageAssetSource;
764
755
  itemId?: string;
765
756
  thumbnail?: string;
766
- persistOnPage: boolean;
767
757
  addedAt: string;
768
758
  /** Stable key for detected entries — used for sync + suppression */
769
759
  detectedKey?: string;
@@ -861,4 +851,14 @@ declare const runAiditorPublishAction: _jay_framework_fullstack_component.JayStr
861
851
 
862
852
  declare function setupAiditor(ctx: PluginSetupContext): Promise<PluginSetupResult>;
863
853
 
864
- export { cancelAgentTaskAction, checkAddPageRouteAction, checkAiditorPublishAction, checkPageAddPageBriefAction, checkPluginUpdateAction, ensureProjectPluginAction, generateAddPageBriefFromImageAction, getAiditorBootstrap, getContractInspectorTagsAction, getPageAssetsAction, getPageMetaAction, getPageParamsAction, getPluginSetupStatusAction, getProjectInfoAction, listAddMenuItemsAction, listFreezesAction, listJayPluginsAction, listPluginContractsAction, openAddPageFromBriefAction, readFileAction, reclassifyAddPageAssetAction, rerunPluginSetupAction, resolveAddMenuContractParamsAction, runAiditorPublishAction, saveAddPageDraftAction, savePageAssetsAction, savePageMetaAction, setupAiditor, startAddPageRequestAction, submitAddPageAction, submitTaskAction, syncAddMenuAttachmentsAction, syncAddPagePluginManifestAction, syncDetectedPageAssetsAction, uploadAddPageAssetAction, writePluginSourceAction };
854
+ declare function listAddMenuItems(projectRoot: string): Promise<ListAddMenuItemsResult>;
855
+
856
+ declare function sanitizeAddMenuHtmlFragment(html: string): string;
857
+
858
+ /** Normalize legacy `thumbnail` and explicit `presentation` to one shape (Design Log #30). */
859
+ declare function normalizeAddMenuPresentation(item: Pick<AddMenuItem, "thumbnail" | "presentation">): AddMenuPresentation | undefined;
860
+
861
+ /** Default browse footprint — Design Log #32. */
862
+ declare function normalizeAddMenuBrowseSize(item: Pick<AddMenuItem, "browse">): AddMenuBrowseSize;
863
+
864
+ export { type AddMenuCatalogWarning, type ListAddMenuItemsResult, cancelAgentTaskAction, checkAddPageRouteAction, checkAiditorPublishAction, checkPageAddPageBriefAction, checkPluginUpdateAction, ensureProjectPluginAction, generateAddPageBriefFromImageAction, getAiditorBootstrap, getContractInspectorTagsAction, getPageAssetsAction, getPageMetaAction, getPageParamsAction, getPluginSetupStatusAction, getProjectInfoAction, listAddMenuItems, listAddMenuItemsAction, listFreezesAction, listJayPluginsAction, listPluginContractsAction, normalizeAddMenuBrowseSize, normalizeAddMenuPresentation, openAddPageFromBriefAction, readFileAction, reclassifyAddPageAssetAction, rerunPluginSetupAction, resolveAddMenuContractParamsAction, runAiditorPublishAction, sanitizeAddMenuHtmlFragment, saveAddPageDraftAction, savePageAssetsAction, savePageMetaAction, setupAiditor, startAddPageRequestAction, submitAddPageAction, submitTaskAction, syncAddMenuAttachmentsAction, syncAddPagePluginManifestAction, syncDetectedPageAssetsAction, uploadAddPageAssetAction, writePluginSourceAction };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1
4
  import { makeJayQuery, makeJayStream, makeJayStackComponent, phaseOutput, RenderPipeline } from "@jay-framework/fullstack-component";
2
5
  import { DEV_SERVER_SERVICE } from "@jay-framework/dev-server";
3
6
  import fs, { readFile, readdir } from "fs/promises";
@@ -6,6 +9,7 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
6
9
  import fs$1 from "fs";
7
10
  import crypto from "crypto";
8
11
  import yaml, { load } from "js-yaml";
12
+ import { validateAddMenuCatalogFile, lintAddMenuCatalog } from "@jay-framework/plugin-validator";
9
13
  import { getLogger } from "@jay-framework/logger";
10
14
  import { fileURLToPath } from "url";
11
15
  import { spawn } from "child_process";
@@ -338,131 +342,10 @@ function groupAddMenuItems(items) {
338
342
  return sa.localeCompare(sb);
339
343
  }).map(([subCategory, subItems]) => ({
340
344
  subCategory,
341
- items: subItems.sort((a, b) => a.title.localeCompare(b.title))
345
+ items: subItems
342
346
  }))
343
347
  }));
344
348
  }
345
- const REJECTED_ITEM_FIELDS = [
346
- "kind",
347
- "parameters",
348
- "component",
349
- "allowedScopes"
350
- ];
351
- function isRecord(value) {
352
- return typeof value === "object" && value !== null && !Array.isArray(value);
353
- }
354
- function requiredString(obj, field, path2, errors) {
355
- const value = obj[field];
356
- if (typeof value !== "string" || value.trim().length === 0) {
357
- errors.push({
358
- path: `${path2}.${field}`,
359
- message: "required non-empty string"
360
- });
361
- return null;
362
- }
363
- return value.trim();
364
- }
365
- function optionalString(obj, field) {
366
- const value = obj[field];
367
- if (value === void 0) return void 0;
368
- if (typeof value !== "string") return void 0;
369
- const trimmed = value.trim();
370
- return trimmed.length > 0 ? trimmed : void 0;
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
- }
396
- function validateAddMenuItem(raw, path2) {
397
- const errors = [];
398
- if (!isRecord(raw)) {
399
- return {
400
- item: null,
401
- errors: [{ path: path2, message: "item must be an object" }]
402
- };
403
- }
404
- for (const field of REJECTED_ITEM_FIELDS) {
405
- if (field in raw) {
406
- errors.push({
407
- path: `${path2}.${field}`,
408
- message: `field "${field}" is not allowed in Add Menu catalog items`
409
- });
410
- }
411
- }
412
- const id = requiredString(raw, "id", path2, errors);
413
- const title = requiredString(raw, "title", path2, errors);
414
- const category = requiredString(raw, "category", path2, errors);
415
- const prompt = requiredString(raw, "prompt", path2, errors);
416
- if (errors.length > 0 || !id || !title || !category || !prompt) {
417
- return { item: null, errors };
418
- }
419
- const interaction = validateInteraction(
420
- raw.interaction,
421
- `${path2}.interaction`,
422
- errors
423
- );
424
- return {
425
- item: {
426
- id,
427
- title,
428
- category,
429
- prompt,
430
- pluginName: optionalString(raw, "pluginName"),
431
- packageName: optionalString(raw, "packageName"),
432
- subCategory: optionalString(raw, "subCategory"),
433
- thumbnail: optionalString(raw, "thumbnail"),
434
- ...interaction ? { interaction } : {}
435
- },
436
- errors
437
- };
438
- }
439
- function validateAddMenuCatalogFile(raw, sourcePath) {
440
- const errors = [];
441
- if (!isRecord(raw)) {
442
- return {
443
- file: null,
444
- errors: [{ path: sourcePath, message: "catalog file must be an object" }]
445
- };
446
- }
447
- if (!Array.isArray(raw.items)) {
448
- return {
449
- file: null,
450
- errors: [
451
- { path: `${sourcePath}.items`, message: "items must be an array" }
452
- ]
453
- };
454
- }
455
- const items = [];
456
- raw.items.forEach((entry, index) => {
457
- const result = validateAddMenuItem(entry, `${sourcePath}.items[${index}]`);
458
- errors.push(...result.errors);
459
- if (result.item) items.push(result.item);
460
- });
461
- if (items.length === 0 && errors.length > 0) {
462
- return { file: null, errors };
463
- }
464
- return { file: { items }, errors };
465
- }
466
349
  function parsePluginsIndexYaml(yaml2) {
467
350
  const entries = [];
468
351
  let currentPlugin = null;
@@ -588,6 +471,7 @@ async function parsePluginYamlContracts(projectRoot, pluginName) {
588
471
  }
589
472
  }
590
473
  const ADD_MENU_DIR = path.join("agent-kit", "aiditor", "add-menu");
474
+ const LOCAL_PLUGINS_DIR = path.join("src", "plugins");
591
475
  function packageNameFromItem(item) {
592
476
  if (item.packageName) return item.packageName;
593
477
  if (item.pluginName) return `@jay-framework/${item.pluginName}`;
@@ -603,6 +487,39 @@ function pluginNameFromItem(item) {
603
487
  if (colon > 0) return item.id.slice(0, colon);
604
488
  return null;
605
489
  }
490
+ async function getLocalPluginNames(projectRoot) {
491
+ const names = /* @__PURE__ */ new Set();
492
+ const pluginsDir = path.join(projectRoot, LOCAL_PLUGINS_DIR);
493
+ let entries;
494
+ try {
495
+ entries = await fs.readdir(pluginsDir);
496
+ } catch (e) {
497
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
498
+ if (code === "ENOENT") return names;
499
+ throw e;
500
+ }
501
+ for (const entry of entries) {
502
+ const pluginYaml = path.join(pluginsDir, entry, "plugin.yaml");
503
+ try {
504
+ const yaml2 = await fs.readFile(pluginYaml, "utf-8");
505
+ const nameMatch = yaml2.match(/^name:\s*(.+)\s*$/m);
506
+ if (nameMatch?.[1]) {
507
+ names.add(nameMatch[1].trim());
508
+ continue;
509
+ }
510
+ } catch {
511
+ }
512
+ names.add(entry);
513
+ }
514
+ return names;
515
+ }
516
+ function validationErrorToWarning(err) {
517
+ return {
518
+ code: err.code ?? "catalog-validation-error",
519
+ message: `${err.path}: ${err.message}`,
520
+ sourcePath: err.path
521
+ };
522
+ }
606
523
  async function listAddMenuItems(projectRoot) {
607
524
  const addMenuDir = path.join(projectRoot, ADD_MENU_DIR);
608
525
  const warnings = [];
@@ -620,43 +537,67 @@ async function listAddMenuItems(projectRoot) {
620
537
  const installed = await getInstalledPlugins(projectRoot);
621
538
  const installedPackages = new Set(installed.map((p) => p.packageName));
622
539
  const installedPlugins = new Set(installed.map((p) => p.pluginName));
540
+ for (const localName of await getLocalPluginNames(projectRoot)) {
541
+ installedPlugins.add(localName);
542
+ }
623
543
  const byId = /* @__PURE__ */ new Map();
544
+ const orderedIds = [];
545
+ const lintBlockedIds = /* @__PURE__ */ new Set();
624
546
  for (const filePath of yamlFiles) {
625
547
  const relPath = path.relative(projectRoot, filePath);
626
548
  let rawContent;
627
549
  try {
628
550
  rawContent = await fs.readFile(filePath, "utf-8");
629
551
  } catch {
630
- warnings.push(`Could not read Add Menu catalog file: ${relPath}`);
552
+ warnings.push({
553
+ code: "catalog-read-error",
554
+ message: `Could not read Add Menu catalog file: ${relPath}`,
555
+ sourcePath: relPath
556
+ });
631
557
  continue;
632
558
  }
633
559
  let parsed;
634
560
  try {
635
561
  parsed = load(rawContent);
636
562
  } catch (err) {
637
- warnings.push(
638
- `Invalid YAML in ${relPath}: ${err instanceof Error ? err.message : String(err)}`
639
- );
563
+ warnings.push({
564
+ code: "catalog-parse-error",
565
+ message: `Invalid YAML in ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
566
+ sourcePath: relPath
567
+ });
640
568
  continue;
641
569
  }
642
570
  const validated = validateAddMenuCatalogFile(parsed, relPath);
643
- if (validated.errors.length > 0) {
644
- for (const err of validated.errors) {
645
- warnings.push(`${err.path}: ${err.message}`);
646
- }
571
+ for (const err of validated.errors) {
572
+ warnings.push(validationErrorToWarning(err));
573
+ }
574
+ if (!validated.file) {
647
575
  continue;
648
576
  }
577
+ const linted = lintAddMenuCatalog(validated.file.items, relPath);
578
+ for (const err of linted.errors) {
579
+ warnings.push(err);
580
+ if (err.itemId) lintBlockedIds.add(err.itemId);
581
+ }
582
+ warnings.push(...linted.warnings);
649
583
  for (const item of validated.file.items) {
584
+ if (lintBlockedIds.has(item.id)) continue;
650
585
  const prev = byId.get(item.id);
651
586
  if (prev) {
652
- warnings.push(
653
- `Duplicate Add Menu item id "${item.id}": "${prev.sourcePath}" overwritten by "${relPath}"`
654
- );
587
+ warnings.push({
588
+ code: "catalog-duplicate-id",
589
+ message: `Duplicate Add Menu item id "${item.id}": "${prev.sourcePath}" overwritten by "${relPath}"`,
590
+ itemId: item.id,
591
+ sourcePath: relPath
592
+ });
593
+ byId.set(item.id, { item, sourcePath: relPath });
594
+ continue;
655
595
  }
656
596
  byId.set(item.id, { item, sourcePath: relPath });
597
+ orderedIds.push(item.id);
657
598
  }
658
599
  }
659
- const merged = [...byId.values()].map((entry) => entry.item);
600
+ const merged = orderedIds.map((id) => byId.get(id)?.item).filter((item) => item !== void 0);
660
601
  const filtered = merged.filter((item) => {
661
602
  const pkg = packageNameFromItem(item);
662
603
  const plugin = pluginNameFromItem(item);
@@ -665,7 +606,7 @@ async function listAddMenuItems(projectRoot) {
665
606
  return false;
666
607
  });
667
608
  return {
668
- items: filtered.sort((a, b) => a.title.localeCompare(b.title)),
609
+ items: filtered,
669
610
  categories: groupAddMenuItems(filtered),
670
611
  warnings
671
612
  };
@@ -938,7 +879,7 @@ function buildRouteMigrationPromptSection(routeMigration) {
938
879
  ].join("\n");
939
880
  }
940
881
  function buildPageAssetsPromptSection(assets) {
941
- const active = assets.filter((a) => a.persistOnPage && !a.userRemoved);
882
+ const active = assets.filter((a) => !a.userRemoved);
942
883
  if (active.length === 0) return "";
943
884
  const lines = [
944
885
  "Page assets (in scope for this page — use when referenced in marker instructions or @mentions):"
@@ -1086,11 +1027,12 @@ function appendVisualAttachmentLines(body, attachments, indent = "") {
1086
1027
  return;
1087
1028
  }
1088
1029
  body.push(
1089
- `${indent}Attachments (read from disk; belong to this annotation only):`
1030
+ `${indent}Attachments (read from disk; belong to this annotation only; match "image #N" in the instruction):`
1090
1031
  );
1091
- for (const att of attachments) {
1032
+ for (let i = 0; i < attachments.length; i++) {
1033
+ const att = attachments[i];
1092
1034
  body.push(
1093
- `${indent}- ${att.taskCopyPath} (filename: ${att.originalFileName})`
1035
+ `${indent}- Image #${i + 1}: ${att.taskCopyPath} (filename: ${att.originalFileName})`
1094
1036
  );
1095
1037
  }
1096
1038
  }
@@ -1265,6 +1207,13 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
1265
1207
  }
1266
1208
  return [...preamble, ...body].join("\n");
1267
1209
  }
1210
+ function distinctTimeSecsAscending(annotations) {
1211
+ const s = /* @__PURE__ */ new Set();
1212
+ for (const a of annotations) {
1213
+ s.add(a.timeSec);
1214
+ }
1215
+ return [...s].sort((a, b) => a - b);
1216
+ }
1268
1217
  function persistFile(file, destDir, fileName) {
1269
1218
  fs$1.mkdirSync(destDir, { recursive: true });
1270
1219
  const dest = path.join(destDir, fileName ?? file.name);
@@ -1309,14 +1258,13 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
1309
1258
  }
1310
1259
  const extra = input.extraFiles ?? {};
1311
1260
  const frames = [];
1261
+ const momentTimes = parsed.structuredVideo ? distinctTimeSecsAscending(parsed.structuredVideo.annotations) : [];
1312
1262
  for (let i = 0; ; i++) {
1313
1263
  const markers = extra[`frame_${i}_markers_only`];
1314
1264
  const clean = extra[`frame_${i}_clean`];
1315
1265
  if (!markers && !clean) break;
1316
1266
  const frameDir = path.join(taskDir, "frames", String(i));
1317
- const timeSec = parsed.structuredVideo?.annotations.find(
1318
- (a, idx) => idx === i || a.timeSec !== void 0
1319
- )?.timeSec ?? i;
1267
+ const timeSec = momentTimes[i] ?? i;
1320
1268
  frames.push({
1321
1269
  timeSec,
1322
1270
  frameIndex: i,
@@ -1456,17 +1404,19 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles().withHan
1456
1404
  });
1457
1405
  const cancelAgentTaskAction = makeJayQuery(
1458
1406
  "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);
1407
+ ).withHandler(
1408
+ async (input) => {
1409
+ const routeKey = input.cancelKey?.trim() || resolveAgentContextKey({
1410
+ pageRoute: input.pageRoute ?? "/",
1411
+ renderedUrl: input.renderedUrl
1412
+ });
1413
+ const cancelled = cancelAgentTask(routeKey);
1414
+ if (!cancelled) {
1415
+ forceReleaseAgentRoute(routeKey);
1416
+ }
1417
+ return { cancelled: true };
1467
1418
  }
1468
- return { cancelled: true };
1469
- });
1419
+ );
1470
1420
  class AddPagePromptLoadError extends Error {
1471
1421
  constructor(message) {
1472
1422
  super(message);
@@ -2292,6 +2242,12 @@ When the image is a formal Figma **design definitions** board, apply these rules
2292
2242
  ${designDefinitionsBody}`;
2293
2243
  });
2294
2244
  }
2245
+ class BriefFillCancelledError extends Error {
2246
+ constructor() {
2247
+ super("Stopped by user.");
2248
+ __publicField(this, "name", "BriefFillCancelledError");
2249
+ }
2250
+ }
2295
2251
  const briefFillModelOverride = process.env.AIDITOR_BRIEF_FILL_MODEL?.trim();
2296
2252
  const ALLOWED_IMAGE_MIMES = /* @__PURE__ */ new Set([
2297
2253
  "image/png",
@@ -2459,6 +2415,9 @@ async function runBriefFillAgentQuery(input) {
2459
2415
  thinking: { type: "disabled" }
2460
2416
  }
2461
2417
  })) {
2418
+ if (input.cancelKey && isAgentTaskCancelled(input.cancelKey)) {
2419
+ throw new BriefFillCancelledError();
2420
+ }
2462
2421
  collected.push(message);
2463
2422
  }
2464
2423
  const rawText = extractBriefFillRawText(collected);
@@ -2487,6 +2446,19 @@ async function generateBriefFromImages(input, options) {
2487
2446
  }
2488
2447
  return { markdown };
2489
2448
  }
2449
+ const BRIEF_FILL_CANCEL_PREFIX = "brief-fill:";
2450
+ function resolveBriefFillCancelKey(input) {
2451
+ const targetSuffix = input.target ? `:${input.target}` : "";
2452
+ const requestId = input.requestId?.trim();
2453
+ if (requestId) {
2454
+ return `${BRIEF_FILL_CANCEL_PREFIX}request:${requestId}${targetSuffix}`;
2455
+ }
2456
+ const route = input.pageRoute?.trim();
2457
+ if (route) {
2458
+ return `${BRIEF_FILL_CANCEL_PREFIX}route:${normalizePageRoute(route)}${targetSuffix}`;
2459
+ }
2460
+ return `${BRIEF_FILL_CANCEL_PREFIX}draft${targetSuffix}`;
2461
+ }
2490
2462
  function persistUpload(file, destPath) {
2491
2463
  fs$1.mkdirSync(path.dirname(destPath), { recursive: true });
2492
2464
  fs$1.copyFileSync(file.path, destPath);
@@ -2826,15 +2798,27 @@ const generateAddPageBriefFromImageAction = makeJayQuery(
2826
2798
  }
2827
2799
  try {
2828
2800
  const images = imageFiles.map(readImageAsBase64);
2829
- const result = await generateBriefFromImages(
2830
- {
2831
- target: input.target,
2832
- images,
2833
- contextNotes: input.contextNotes,
2834
- pageRoute: input.pageRoute
2835
- },
2836
- { projectDir }
2837
- );
2801
+ const cancelKey = resolveBriefFillCancelKey({
2802
+ requestId: input.requestId,
2803
+ pageRoute: input.pageRoute,
2804
+ target: input.target
2805
+ });
2806
+ const cancelToken = beginCancellableAgentTask(cancelKey);
2807
+ let result;
2808
+ try {
2809
+ result = await generateBriefFromImages(
2810
+ {
2811
+ target: input.target,
2812
+ images,
2813
+ contextNotes: input.contextNotes,
2814
+ pageRoute: input.pageRoute,
2815
+ cancelKey
2816
+ },
2817
+ { projectDir }
2818
+ );
2819
+ } finally {
2820
+ endCancellableAgentTask(cancelKey, cancelToken);
2821
+ }
2838
2822
  const referenceAttachments = [];
2839
2823
  if (input.requestId) {
2840
2824
  for (const file of imageFiles) {
@@ -2863,6 +2847,9 @@ const generateAddPageBriefFromImageAction = makeJayQuery(
2863
2847
  referenceAttachments
2864
2848
  };
2865
2849
  } catch (err) {
2850
+ if (err instanceof BriefFillCancelledError) {
2851
+ return { ok: false, error: err.message };
2852
+ }
2866
2853
  return {
2867
2854
  ok: false,
2868
2855
  error: err instanceof Error ? err.message : String(err)
@@ -4374,7 +4361,6 @@ function detectedToPageAssetEntry(detected) {
4374
4361
  prompt: detected.prompt,
4375
4362
  source: "detected",
4376
4363
  itemId: detected.pluginName ? `${detected.pluginName}:${detected.contractName ?? ""}` : void 0,
4377
- persistOnPage: true,
4378
4364
  addedAt: (/* @__PURE__ */ new Date()).toISOString(),
4379
4365
  detectedKey: detected.detectedKey,
4380
4366
  detectedMeta: {
@@ -4596,6 +4582,34 @@ async function setupAiditor(ctx) {
4596
4582
  ...filesUpdated.length > 0 ? { filesUpdated } : {}
4597
4583
  };
4598
4584
  }
4585
+ const BLOCKED_TAGS = /<\s*(script|iframe|object|embed)\b[^>]*>[\s\S]*?<\/\s*\1\s*>|<\s*(script|iframe|object|embed)\b[^>]*\/?>/gi;
4586
+ const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
4587
+ const JAVASCRIPT_URL = /\b(href|src|xlink:href)\s*=\s*("|')\s*javascript:/gi;
4588
+ function sanitizeAddMenuHtmlFragment(html) {
4589
+ let result = html;
4590
+ result = result.replace(BLOCKED_TAGS, "");
4591
+ BLOCKED_TAGS.lastIndex = 0;
4592
+ result = result.replace(EVENT_HANDLER_ATTR, "");
4593
+ EVENT_HANDLER_ATTR.lastIndex = 0;
4594
+ result = result.replace(JAVASCRIPT_URL, "");
4595
+ JAVASCRIPT_URL.lastIndex = 0;
4596
+ return result.trim();
4597
+ }
4598
+ function isGifPath(src) {
4599
+ return /\.gif($|\?)/i.test(src.trim());
4600
+ }
4601
+ function normalizeAddMenuPresentation(item) {
4602
+ if (item.presentation) return item.presentation;
4603
+ const thumbnail = item.thumbnail?.trim();
4604
+ if (!thumbnail) return void 0;
4605
+ if (isGifPath(thumbnail)) {
4606
+ return { type: "gif", src: thumbnail };
4607
+ }
4608
+ return { type: "image", src: thumbnail };
4609
+ }
4610
+ function normalizeAddMenuBrowseSize(item) {
4611
+ return item.browse?.size ?? "medium";
4612
+ }
4599
4613
  const aiditorShell = makeJayStackComponent().withProps().withSlowlyRender(async () => {
4600
4614
  return phaseOutput({}, { headline: "AIditor" });
4601
4615
  }).withFastRender(async (_props, carryForward) => {
@@ -4626,16 +4640,20 @@ export {
4626
4640
  getPageParamsAction,
4627
4641
  getPluginSetupStatusAction,
4628
4642
  getProjectInfoAction,
4643
+ listAddMenuItems,
4629
4644
  listAddMenuItemsAction,
4630
4645
  listFreezesAction,
4631
4646
  listJayPluginsAction,
4632
4647
  listPluginContractsAction,
4648
+ normalizeAddMenuBrowseSize,
4649
+ normalizeAddMenuPresentation,
4633
4650
  openAddPageFromBriefAction,
4634
4651
  readFileAction,
4635
4652
  reclassifyAddPageAssetAction,
4636
4653
  rerunPluginSetupAction,
4637
4654
  resolveAddMenuContractParamsAction,
4638
4655
  runAiditorPublishAction,
4656
+ sanitizeAddMenuHtmlFragment,
4639
4657
  saveAddPageDraftAction,
4640
4658
  savePageAssetsAction,
4641
4659
  savePageMetaAction,