@jay-framework/stack-server-runtime 0.12.0 → 0.14.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +243 -93
  2. package/dist/index.js +989 -272
  3. package/package.json +13 -12
package/dist/index.js CHANGED
@@ -5,19 +5,19 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
7
  import { notFound, partialRender, phaseOutput, isJayAction } from "@jay-framework/fullstack-component";
8
- import fs$1 from "node:fs/promises";
9
- import * as path from "node:path";
10
- import path__default from "node:path";
11
- import { parseJayFile, JAY_IMPORT_RESOLVER } from "@jay-framework/compiler-jay-html";
12
- import { createRequire } from "module";
13
- import * as fs from "node:fs";
14
- import { createRequire as createRequire$1 } from "node:module";
15
8
  import "prettier";
16
9
  import "js-beautify";
17
- import fs$2 from "fs";
10
+ import fs$1 from "fs";
18
11
  import path$1 from "path";
19
12
  import YAML from "yaml";
13
+ import { createRequire } from "module";
14
+ import { parseJayFile, JAY_IMPORT_RESOLVER, generateServerElementFile, discoverHeadlessInstances, parseAction } from "@jay-framework/compiler-jay-html";
15
+ import fs$2 from "node:fs/promises";
16
+ import * as path from "node:path";
17
+ import path__default from "node:path";
20
18
  import { getLogger } from "@jay-framework/logger";
19
+ import * as fs from "node:fs";
20
+ import { createRequire as createRequire$1 } from "node:module";
21
21
  import crypto from "node:crypto";
22
22
  const serviceRegistry = /* @__PURE__ */ new Map();
23
23
  function registerService(marker, service) {
@@ -86,32 +86,42 @@ function clearClientInitData() {
86
86
  function isLeftSideParamsSubsetOfRightSideParams(left, right) {
87
87
  return Object.keys(left).reduce((prev, curr) => prev && left[curr] === right[curr], true);
88
88
  }
89
- async function findMatchingParams(search, searchTarget) {
90
- for await (const paramsArray of searchTarget) {
91
- if (paramsArray.find((params) => isLeftSideParamsSubsetOfRightSideParams(search, params)))
92
- return true;
93
- }
94
- return false;
95
- }
96
89
  class DevSlowlyChangingPhase {
97
- constructor(dontCacheSlowly) {
98
- this.dontCacheSlowly = dontCacheSlowly;
99
- }
100
- async runSlowlyForPage(pageParams, pageProps, parts) {
101
- for (const part of parts) {
102
- const { compDefinition, contractInfo } = part;
103
- if (compDefinition.loadParams) {
104
- const services = resolveServices(compDefinition.services);
105
- const loadParamsArgs = contractInfo ? [
106
- ...services,
107
- {
108
- contractName: contractInfo.contractName,
109
- metadata: contractInfo.metadata
90
+ constructor() {
91
+ /** Cached loadParams results per route (jayHtmlPath → array of UrlParams[] per part) */
92
+ __publicField(this, "loadParamsCache", /* @__PURE__ */ new Map());
93
+ }
94
+ async runSlowlyForPage(pageParams, pageProps, parts, discoveredInstances, headlessInstanceComponents, jayHtmlPath) {
95
+ const loadParamsParts = parts.filter((p) => p.compDefinition.loadParams);
96
+ if (loadParamsParts.length > 0) {
97
+ let cachedParamsArray = jayHtmlPath ? this.loadParamsCache.get(jayHtmlPath) : void 0;
98
+ if (!cachedParamsArray) {
99
+ cachedParamsArray = [];
100
+ for (const part of loadParamsParts) {
101
+ const { compDefinition, contractInfo } = part;
102
+ const services = resolveServices(compDefinition.services);
103
+ const loadParamsArgs = contractInfo ? [
104
+ ...services,
105
+ {
106
+ contractName: contractInfo.contractName,
107
+ metadata: contractInfo.metadata
108
+ }
109
+ ] : services;
110
+ const compParams = compDefinition.loadParams(loadParamsArgs);
111
+ const collected = [];
112
+ for await (const batch of compParams) {
113
+ collected.push(...batch);
110
114
  }
111
- ] : services;
112
- const compParams = compDefinition.loadParams(loadParamsArgs);
113
- if (!await findMatchingParams(pageParams, compParams))
115
+ cachedParamsArray.push(collected);
116
+ }
117
+ if (jayHtmlPath) {
118
+ this.loadParamsCache.set(jayHtmlPath, cachedParamsArray);
119
+ }
120
+ }
121
+ for (const partParams of cachedParamsArray) {
122
+ if (!partParams.some((p) => isLeftSideParamsSubsetOfRightSideParams(pageParams, p))) {
114
123
  return notFound();
124
+ }
115
125
  }
116
126
  }
117
127
  let slowlyViewState = {};
@@ -144,15 +154,264 @@ class DevSlowlyChangingPhase {
144
154
  return slowlyRenderedPart;
145
155
  }
146
156
  }
157
+ if (discoveredInstances && discoveredInstances.length > 0 && headlessInstanceComponents) {
158
+ const componentByContractName = /* @__PURE__ */ new Map();
159
+ for (const comp of headlessInstanceComponents) {
160
+ componentByContractName.set(comp.contractName, comp);
161
+ }
162
+ const instancePhaseData = {
163
+ discovered: [],
164
+ carryForwards: {}
165
+ };
166
+ const instanceSlowViewStates = {};
167
+ const instanceResolvedData = [];
168
+ for (const instance of discoveredInstances) {
169
+ const comp = componentByContractName.get(instance.contractName);
170
+ if (!comp)
171
+ continue;
172
+ const coordKey = instance.coordinate.join("/");
173
+ const contractProps = comp.contract?.props ?? [];
174
+ const normalizedProps = {};
175
+ for (const [key, value] of Object.entries(instance.props)) {
176
+ const match = contractProps.find(
177
+ (p) => p.name.toLowerCase() === key.toLowerCase()
178
+ );
179
+ normalizedProps[match ? match.name : key] = value;
180
+ }
181
+ instancePhaseData.discovered.push({
182
+ contractName: instance.contractName,
183
+ props: normalizedProps,
184
+ coordinate: instance.coordinate
185
+ });
186
+ if (comp.compDefinition.slowlyRender) {
187
+ const services = resolveServices(comp.compDefinition.services);
188
+ const slowResult = await comp.compDefinition.slowlyRender(
189
+ normalizedProps,
190
+ ...services
191
+ );
192
+ if (slowResult.kind === "PhaseOutput") {
193
+ instanceSlowViewStates[coordKey] = slowResult.rendered;
194
+ instancePhaseData.carryForwards[coordKey] = slowResult.carryForward;
195
+ instanceResolvedData.push({
196
+ coordinate: instance.coordinate,
197
+ contract: comp.contract,
198
+ slowViewState: slowResult.rendered
199
+ });
200
+ }
201
+ }
202
+ }
203
+ carryForward.__instances = instancePhaseData;
204
+ carryForward.__instanceSlowViewStates = instanceSlowViewStates;
205
+ carryForward.__instanceResolvedData = instanceResolvedData;
206
+ }
147
207
  return partialRender(slowlyViewState, carryForward);
148
208
  }
209
+ /**
210
+ * Invalidate the cached loadParams result for a given route.
211
+ * Called when source files change (page.ts, .jay-contract, .jay-html).
212
+ */
213
+ invalidateLoadParamsCache(jayHtmlPath) {
214
+ this.loadParamsCache.delete(jayHtmlPath);
215
+ }
149
216
  }
150
217
  async function runLoadParams(compDefinition, services) {
151
218
  compDefinition.loadParams(services);
152
219
  }
153
220
  function runSlowlyChangingRender(compDefinition) {
154
221
  }
155
- async function renderFastChangingData(pageParams, pageProps, carryForward, parts) {
222
+ var __defProp2 = Object.defineProperty;
223
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
224
+ var __publicField2 = (obj, key, value) => {
225
+ __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
226
+ return value;
227
+ };
228
+ class JayAtomicType {
229
+ constructor(name) {
230
+ __publicField2(this, "kind", 0);
231
+ this.name = name;
232
+ }
233
+ }
234
+ new JayAtomicType("string");
235
+ new JayAtomicType("number");
236
+ new JayAtomicType("boolean");
237
+ new JayAtomicType("Date");
238
+ new JayAtomicType("Unknown");
239
+ class JayObjectType {
240
+ constructor(name, props) {
241
+ __publicField2(this, "kind", 8);
242
+ this.name = name;
243
+ this.props = props;
244
+ }
245
+ }
246
+ new JayObjectType("Error", {
247
+ message: new JayAtomicType("string"),
248
+ name: new JayAtomicType("string"),
249
+ stack: new JayAtomicType("string")
250
+ });
251
+ function isOptionalType(aType) {
252
+ return aType.kind === 13;
253
+ }
254
+ function isAtomicType(aType) {
255
+ return aType.kind === 0;
256
+ }
257
+ function isEnumType(aType) {
258
+ return aType.kind === 2;
259
+ }
260
+ function isImportedType(aType) {
261
+ return aType.kind === 4;
262
+ }
263
+ function isObjectType(aType) {
264
+ return aType.kind === 8;
265
+ }
266
+ function isArrayType(aType) {
267
+ return aType.kind === 9;
268
+ }
269
+ function jayTypeToJsonSchema(type) {
270
+ if (isOptionalType(type)) {
271
+ return jayTypeToJsonSchema(type.innerType);
272
+ }
273
+ if (isAtomicType(type)) {
274
+ const name = type.name.toLowerCase();
275
+ if (name === "string" || name === "number" || name === "boolean") {
276
+ return { type: name };
277
+ }
278
+ return { type: "string" };
279
+ }
280
+ if (isEnumType(type)) {
281
+ return { type: "string", enum: type.values };
282
+ }
283
+ if (isImportedType(type)) {
284
+ return { type: "object", description: `Contract: ${type.name}` };
285
+ }
286
+ if (isArrayType(type)) {
287
+ const itemSchema = jayTypeToJsonSchema(type.itemType);
288
+ if (itemSchema) {
289
+ return { type: "array", items: itemSchema };
290
+ }
291
+ return { type: "array" };
292
+ }
293
+ if (isObjectType(type)) {
294
+ const properties = {};
295
+ const required = [];
296
+ for (const [key, propType] of Object.entries(type.props)) {
297
+ const isOpt = isOptionalType(propType);
298
+ const schema = jayTypeToJsonSchema(propType);
299
+ if (schema) {
300
+ properties[key] = schema;
301
+ if (!isOpt) {
302
+ required.push(key);
303
+ }
304
+ }
305
+ }
306
+ return {
307
+ type: "object",
308
+ properties,
309
+ ...required.length > 0 && { required }
310
+ };
311
+ }
312
+ return null;
313
+ }
314
+ var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
315
+ RuntimeMode2["MainTrusted"] = "mainTrusted";
316
+ RuntimeMode2["MainSandbox"] = "mainSandbox";
317
+ RuntimeMode2["WorkerTrusted"] = "workerTrusted";
318
+ RuntimeMode2["WorkerSandbox"] = "workerSandbox";
319
+ return RuntimeMode2;
320
+ })(RuntimeMode || {});
321
+ const TS_EXTENSION = ".ts";
322
+ const JAY_QUERY_PREFIX = "?jay-";
323
+ const JAY_QUERY_HYDRATE = `${JAY_QUERY_PREFIX}hydrate`;
324
+ [
325
+ // Hydrate target
326
+ {
327
+ pattern: JAY_QUERY_HYDRATE,
328
+ buildEnv: "client",
329
+ isHydrate: true
330
+ },
331
+ // Build environments
332
+ {
333
+ pattern: `${JAY_QUERY_PREFIX}${"client"}`,
334
+ buildEnv: "client"
335
+ /* Client */
336
+ },
337
+ {
338
+ pattern: `${JAY_QUERY_PREFIX}${"server"}`,
339
+ buildEnv: "server"
340
+ /* Server */
341
+ },
342
+ // Runtime modes (with .ts suffix)
343
+ {
344
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
345
+ runtimeMode: RuntimeMode.MainSandbox
346
+ },
347
+ {
348
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
349
+ runtimeMode: RuntimeMode.WorkerTrusted
350
+ },
351
+ {
352
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
353
+ runtimeMode: RuntimeMode.WorkerSandbox
354
+ },
355
+ // Runtime modes (without .ts suffix)
356
+ {
357
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
358
+ runtimeMode: RuntimeMode.MainSandbox
359
+ },
360
+ {
361
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
362
+ runtimeMode: RuntimeMode.WorkerTrusted
363
+ },
364
+ {
365
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
366
+ runtimeMode: RuntimeMode.WorkerSandbox
367
+ }
368
+ ];
369
+ function checkValidationErrors(withValidations) {
370
+ const { validations } = withValidations;
371
+ if (validations.length > 0) {
372
+ throw new Error(validations.join("\n"));
373
+ }
374
+ return withValidations.val;
375
+ }
376
+ createRequire(import.meta.url);
377
+ function normalizeActionEntry(entry) {
378
+ if (typeof entry === "string") {
379
+ return { name: entry };
380
+ }
381
+ return { name: entry.name, action: entry.action };
382
+ }
383
+ function loadPluginManifest(pluginDir) {
384
+ const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
385
+ if (!fs$1.existsSync(pluginYamlPath)) {
386
+ return null;
387
+ }
388
+ try {
389
+ const yamlContent = fs$1.readFileSync(pluginYamlPath, "utf-8");
390
+ return YAML.parse(yamlContent);
391
+ } catch (error) {
392
+ return null;
393
+ }
394
+ }
395
+ function computeForEachInstanceKey(trackByValue, coordinateSuffix) {
396
+ return [trackByValue, coordinateSuffix].toString();
397
+ }
398
+ const s = createRequire(import.meta.url), e = s("typescript");
399
+ new Proxy(e, {
400
+ get(t, r) {
401
+ return t[r];
402
+ }
403
+ });
404
+ function resolvePathValue(obj, path2) {
405
+ return path2.split(".").reduce((current, segment) => current?.[segment], obj);
406
+ }
407
+ function resolveBinding(binding, item) {
408
+ const match = binding.match(/^\{(.+)\}$/);
409
+ if (match) {
410
+ return String(item[match[1]] ?? "");
411
+ }
412
+ return binding;
413
+ }
414
+ async function renderFastChangingData(pageParams, pageProps, carryForward, parts, instancePhaseData, forEachInstances, headlessInstanceComponents, mergedSlowViewState) {
156
415
  let fastViewState = {};
157
416
  let fastCarryForward = {};
158
417
  for (const part of parts) {
@@ -185,12 +444,92 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
185
444
  return fastRenderedPart;
186
445
  }
187
446
  }
447
+ const instanceViewStates = {};
448
+ const instanceCarryForwards = {};
449
+ if (instancePhaseData && headlessInstanceComponents) {
450
+ const componentByContractName = /* @__PURE__ */ new Map();
451
+ for (const comp of headlessInstanceComponents) {
452
+ componentByContractName.set(comp.contractName, comp);
453
+ }
454
+ for (const instance of instancePhaseData.discovered) {
455
+ const coordKey = instance.coordinate.join("/");
456
+ const comp = componentByContractName.get(instance.contractName);
457
+ if (!comp || !comp.compDefinition.fastRender)
458
+ continue;
459
+ const services = resolveServices(comp.compDefinition.services);
460
+ const cf = instancePhaseData.carryForwards[coordKey];
461
+ const fastResult = comp.compDefinition.slowlyRender ? await comp.compDefinition.fastRender(instance.props, cf, ...services) : await comp.compDefinition.fastRender(instance.props, ...services);
462
+ if (fastResult.kind === "PhaseOutput") {
463
+ const instanceSlowVS = carryForward?.__instanceSlowViewStates?.[coordKey];
464
+ instanceViewStates[coordKey] = instanceSlowVS ? { ...instanceSlowVS, ...fastResult.rendered } : fastResult.rendered;
465
+ if (fastResult.carryForward) {
466
+ instanceCarryForwards[coordKey] = fastResult.carryForward;
467
+ }
468
+ }
469
+ }
470
+ }
471
+ if (forEachInstances && forEachInstances.length > 0 && headlessInstanceComponents) {
472
+ const componentByContractName = /* @__PURE__ */ new Map();
473
+ for (const comp of headlessInstanceComponents) {
474
+ componentByContractName.set(comp.contractName, comp);
475
+ }
476
+ const mergedForEachVS = { ...mergedSlowViewState || {}, ...fastViewState };
477
+ for (const instance of forEachInstances) {
478
+ const comp = componentByContractName.get(instance.contractName);
479
+ if (!comp)
480
+ continue;
481
+ const items = resolvePathValue(mergedForEachVS, instance.forEachPath);
482
+ if (!Array.isArray(items))
483
+ continue;
484
+ const contractProps = comp.contract?.props ?? [];
485
+ const normalizePropName = (key) => contractProps.find((p) => p.name.toLowerCase() === key.toLowerCase())?.name ?? key;
486
+ for (const item of items) {
487
+ const trackByValue = String(item[instance.trackBy]);
488
+ const props = {};
489
+ for (const [propName, binding] of Object.entries(instance.propBindings)) {
490
+ props[normalizePropName(propName)] = resolveBinding(String(binding), item);
491
+ }
492
+ if (comp.compDefinition.fastRender) {
493
+ const services = resolveServices(comp.compDefinition.services);
494
+ let slowVS = {};
495
+ let cf = {};
496
+ if (comp.compDefinition.slowlyRender) {
497
+ const slowResult = await comp.compDefinition.slowlyRender(
498
+ props,
499
+ ...services
500
+ );
501
+ if (slowResult.kind === "PhaseOutput") {
502
+ slowVS = slowResult.rendered;
503
+ cf = slowResult.carryForward;
504
+ }
505
+ }
506
+ const fastResult = comp.compDefinition.slowlyRender ? await comp.compDefinition.fastRender(props, cf, ...services) : await comp.compDefinition.fastRender(props, ...services);
507
+ if (fastResult.kind === "PhaseOutput") {
508
+ const coord = computeForEachInstanceKey(
509
+ trackByValue,
510
+ instance.coordinateSuffix
511
+ );
512
+ instanceViewStates[coord] = { ...slowVS, ...fastResult.rendered };
513
+ if (fastResult.carryForward) {
514
+ instanceCarryForwards[coord] = fastResult.carryForward;
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ }
521
+ if (Object.keys(instanceViewStates).length > 0) {
522
+ fastViewState.__headlessInstances = instanceViewStates;
523
+ }
524
+ if (Object.keys(instanceCarryForwards).length > 0) {
525
+ fastCarryForward.__headlessInstances = instanceCarryForwards;
526
+ }
188
527
  return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
189
528
  }
190
- function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
529
+ function buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options) {
191
530
  const { enableAutomation = true, slowViewState } = options;
192
531
  const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
193
- const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
532
+ const partImports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
194
533
  const compositeParts = parts.length > 0 ? `[
195
534
  ${parts.map((part) => " " + part.clientPart).join(",\n")}
196
535
  ]` : "[]";
@@ -214,7 +553,7 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
214
553
  const clientInitExecution = hasClientInit ? `
215
554
  // Plugin client initialization (in dependency order)
216
555
  ${pluginClientInitCalls}
217
-
556
+
218
557
  // Project client initialization
219
558
  ${projectInitCall}
220
559
  ` : "";
@@ -223,7 +562,28 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
223
562
  import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
224
563
  import { registerGlobalContext } from "@jay-framework/runtime";` : "";
225
564
  const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
226
- const automationWrap = enableAutomation ? hasSlowViewState ? `
565
+ return {
566
+ partImports,
567
+ compositeParts,
568
+ pluginClientInitImports,
569
+ projectInitImport,
570
+ clientInitExecution,
571
+ automationImport,
572
+ slowViewStateDecl
573
+ };
574
+ }
575
+ function buildAutomationWrap(options, mode) {
576
+ const { enableAutomation = true, slowViewState } = options;
577
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
578
+ const appendDom = mode === "client";
579
+ if (!enableAutomation) {
580
+ return appendDom ? `
581
+ target.appendChild(instance.element.dom);` : "";
582
+ }
583
+ const appendLine = appendDom ? `
584
+ target.appendChild(wrapped.element.dom);` : "";
585
+ if (hasSlowViewState) {
586
+ return `
227
587
  // Wrap with automation for dev tooling
228
588
  // Deep merge slow+fast ViewState so automation can see full page state
229
589
  const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
@@ -231,14 +591,27 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
231
591
  registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
232
592
  window.__jay = window.__jay || {};
233
593
  window.__jay.automation = wrapped.automation;
234
- target.appendChild(wrapped.element.dom);` : `
594
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}`;
595
+ }
596
+ return `
235
597
  // Wrap with automation for dev tooling
236
598
  const wrapped = wrapWithAutomation(instance);
237
599
  registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
238
600
  window.__jay = window.__jay || {};
239
601
  window.__jay.automation = wrapped.automation;
240
- target.appendChild(wrapped.element.dom);` : `
241
- target.appendChild(instance.element.dom);`;
602
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}`;
603
+ }
604
+ function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
605
+ const {
606
+ partImports,
607
+ compositeParts,
608
+ pluginClientInitImports,
609
+ projectInitImport,
610
+ clientInitExecution,
611
+ automationImport,
612
+ slowViewStateDecl
613
+ } = buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options);
614
+ const automationWrap = buildAutomationWrap(options, "client");
242
615
  return `<!doctype html>
243
616
  <html lang="en">
244
617
  <head>
@@ -254,7 +627,7 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
254
627
  ${pluginClientInitImports}
255
628
  ${projectInitImport}
256
629
  import { render } from '${jayHtmlPath}';
257
- ${imports}${slowViewStateDecl}
630
+ ${partImports}${slowViewStateDecl}
258
631
  const viewState = ${JSON.stringify(defaultViewState)};
259
632
  const fastCarryForward = ${JSON.stringify(fastCarryForward)};
260
633
  const trackByMap = ${JSON.stringify(trackByMap)};
@@ -268,9 +641,171 @@ ${automationWrap}
268
641
  </body>
269
642
  </html>`;
270
643
  }
644
+ const serverModuleCache = /* @__PURE__ */ new Map();
645
+ function invalidateServerElementCache(jayHtmlPath) {
646
+ if (serverModuleCache.delete(jayHtmlPath)) {
647
+ getLogger().info(`[SSR] Invalidated server element cache for ${jayHtmlPath}`);
648
+ }
649
+ }
650
+ function clearServerElementCache() {
651
+ serverModuleCache.clear();
652
+ }
653
+ async function generateSSRPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, jayHtmlImportPath, parts, carryForward, trackByMap = {}, clientInitData2 = {}, buildFolder, projectRoot, routeDir, tsConfigFilePath, projectInit, pluginInits = [], options = {}) {
654
+ const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
655
+ let cached = serverModuleCache.get(jayHtmlPath);
656
+ if (!cached) {
657
+ cached = await compileAndLoadServerElement(
658
+ vite,
659
+ jayHtmlContent,
660
+ jayHtmlFilename,
661
+ jayHtmlDir,
662
+ buildFolder,
663
+ projectRoot,
664
+ routeDir,
665
+ tsConfigFilePath
666
+ );
667
+ serverModuleCache.set(jayHtmlPath, cached);
668
+ }
669
+ const htmlChunks = [];
670
+ const asyncPromises = [];
671
+ const ctx = {
672
+ write: (chunk) => {
673
+ htmlChunks.push(chunk);
674
+ },
675
+ onAsync: (promise, id, templates) => {
676
+ const asyncPromise = promise.then(
677
+ (val) => {
678
+ if (templates.resolved) {
679
+ return templates.resolved(val);
680
+ }
681
+ return "";
682
+ },
683
+ (err) => {
684
+ if (templates.rejected) {
685
+ return templates.rejected(err);
686
+ }
687
+ return "";
688
+ }
689
+ );
690
+ asyncPromises.push(asyncPromise);
691
+ }
692
+ };
693
+ cached.renderToStream(viewState, ctx);
694
+ const asyncResults = await Promise.all(asyncPromises);
695
+ const ssrHtml = htmlChunks.join("");
696
+ const asyncScripts = asyncResults.filter((r) => r !== "").join("");
697
+ const hydrationScript = generateHydrationScript(
698
+ viewState,
699
+ carryForward,
700
+ parts,
701
+ jayHtmlImportPath,
702
+ trackByMap,
703
+ clientInitData2,
704
+ projectInit,
705
+ pluginInits,
706
+ options
707
+ );
708
+ const headLinksHtml = cached.headLinks.map((link) => {
709
+ const attrs = Object.entries(link.attributes).map(([k, v]) => ` ${k}="${v}"`).join("");
710
+ return ` <link rel="${link.rel}" href="${link.href}"${attrs} />`;
711
+ }).join("\n");
712
+ const inlineCss = cached.css ? ` <style>
713
+ ${cached.css}
714
+ </style>` : "";
715
+ const headExtras = [headLinksHtml, inlineCss].filter((_) => _).join("\n");
716
+ return `<!doctype html>
717
+ <html lang="en">
718
+ <head>
719
+ <meta charset="UTF-8" />
720
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
721
+ <title>Vite + TS</title>
722
+ ${headExtras ? headExtras + "\n" : ""} </head>
723
+ <body>
724
+ <div id="target">${ssrHtml}</div>${asyncScripts}
725
+ ${hydrationScript}
726
+ </body>
727
+ </html>`;
728
+ }
729
+ function rebaseRelativeImports(code, fromDir, toDir) {
730
+ return code.replace(/from "(\.\.\/[^"]+)"/g, (_match, relPath) => {
731
+ const absolutePath = path__default.resolve(fromDir, relPath);
732
+ let newRelPath = path__default.relative(toDir, absolutePath);
733
+ if (!newRelPath.startsWith(".")) {
734
+ newRelPath = "./" + newRelPath;
735
+ }
736
+ return `from "${newRelPath}"`;
737
+ });
738
+ }
739
+ async function compileAndLoadServerElement(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, buildFolder, projectRoot, routeDir, tsConfigFilePath) {
740
+ const jayFile = await parseJayFile(
741
+ jayHtmlContent,
742
+ jayHtmlFilename,
743
+ jayHtmlDir,
744
+ { relativePath: tsConfigFilePath },
745
+ JAY_IMPORT_RESOLVER,
746
+ projectRoot
747
+ );
748
+ const parsedJayFile = checkValidationErrors(jayFile);
749
+ const pageName = jayHtmlFilename.replace(".jay-html", "");
750
+ const debugCoordinatePreprocessPath = path__default.join(
751
+ buildFolder,
752
+ "debug",
753
+ routeDir,
754
+ `${pageName}.coordinate-preprocess.jay-html`
755
+ );
756
+ const serverElementOptions = {
757
+ debugCoordinatePreprocessPath
758
+ };
759
+ const serverElementCode = checkValidationErrors(
760
+ generateServerElementFile(parsedJayFile, serverElementOptions)
761
+ );
762
+ const serverElementDir = path__default.join(buildFolder, "pre-rendered", routeDir);
763
+ await fs$2.mkdir(serverElementDir, { recursive: true });
764
+ const adjustedCode = rebaseRelativeImports(serverElementCode, jayHtmlDir, serverElementDir);
765
+ const serverElementFilename = jayHtmlFilename.replace(".jay-html", ".server-element.ts");
766
+ const serverElementPath = path__default.join(serverElementDir, serverElementFilename);
767
+ await fs$2.writeFile(serverElementPath, adjustedCode, "utf-8");
768
+ const serverModule = await vite.ssrLoadModule(serverElementPath);
769
+ return {
770
+ renderToStream: serverModule.renderToStream,
771
+ headLinks: parsedJayFile.headLinks,
772
+ css: parsedJayFile.css
773
+ };
774
+ }
775
+ function generateHydrationScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
776
+ const {
777
+ partImports,
778
+ compositeParts,
779
+ pluginClientInitImports,
780
+ projectInitImport,
781
+ clientInitExecution,
782
+ automationImport,
783
+ slowViewStateDecl
784
+ } = buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options);
785
+ const automationWrap = buildAutomationWrap(options, "hydrate");
786
+ const hydrateImportPath = `${jayHtmlPath}${JAY_QUERY_HYDRATE}`;
787
+ return `<script type="module">
788
+ import {hydrateCompositeJayComponent} from "@jay-framework/stack-client-runtime";
789
+ ${automationImport}
790
+ ${pluginClientInitImports}
791
+ ${projectInitImport}
792
+ import { hydrate } from '${hydrateImportPath}';
793
+ ${partImports}${slowViewStateDecl}
794
+ const viewState = ${JSON.stringify(defaultViewState)};
795
+ const fastCarryForward = ${JSON.stringify(fastCarryForward)};
796
+ const trackByMap = ${JSON.stringify(trackByMap)};
797
+
798
+ const target = document.getElementById('target');
799
+ const rootElement = target.firstElementChild;
800
+ const pageComp = hydrateCompositeJayComponent(hydrate, viewState, fastCarryForward, ${compositeParts}, trackByMap, rootElement);
801
+ ${clientInitExecution}
802
+ const instance = pageComp({/* placeholder for page props */});
803
+ ${automationWrap}
804
+ <\/script>`;
805
+ }
271
806
  const require$3 = createRequire(import.meta.url);
272
807
  async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
273
- const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
808
+ const exists = await fs$2.access(route.compPath, fs$2.constants.F_OK).then(() => true).catch(() => false);
274
809
  const parts = [];
275
810
  if (exists) {
276
811
  const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
@@ -281,7 +816,7 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
281
816
  });
282
817
  }
283
818
  const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
284
- const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
819
+ const jayHtmlSource = options?.preRenderedContent ?? (await fs$2.readFile(jayHtmlFilePath)).toString();
285
820
  const fileName = path__default.basename(route.jayHtmlPath);
286
821
  const dirName = path__default.dirname(route.jayHtmlPath);
287
822
  const jayHtmlWithValidations = await parseJayFile(
@@ -338,13 +873,16 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
338
873
  contract: hi.contract,
339
874
  contractPath: hi.contractPath
340
875
  }));
876
+ const discoveryResult = headlessInstanceComponents.length > 0 ? discoverHeadlessInstances(jayHtmlSource) : { instances: [], forEachInstances: [], preRenderedJayHtml: jayHtmlSource };
341
877
  return {
342
878
  parts,
343
879
  serverTrackByMap: jayHtml.serverTrackByMap,
344
880
  clientTrackByMap: jayHtml.clientTrackByMap,
345
881
  usedPackages,
346
882
  headlessContracts,
347
- headlessInstanceComponents
883
+ headlessInstanceComponents,
884
+ discoveredInstances: discoveryResult.instances,
885
+ forEachInstances: discoveryResult.forEachInstances
348
886
  };
349
887
  });
350
888
  }
@@ -362,8 +900,14 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
362
900
  if (!comp || !comp.compDefinition.slowlyRender) {
363
901
  continue;
364
902
  }
903
+ const contractProps = comp.contract?.props ?? [];
904
+ const normalizedProps = {};
905
+ for (const [key, value] of Object.entries(instance.props)) {
906
+ const match = contractProps.find((p) => p.name.toLowerCase() === key.toLowerCase());
907
+ normalizedProps[match ? match.name : key] = value;
908
+ }
365
909
  const services = resolveServices(comp.compDefinition.services);
366
- const slowResult = await comp.compDefinition.slowlyRender(instance.props, ...services);
910
+ const slowResult = await comp.compDefinition.slowlyRender(normalizedProps, ...services);
367
911
  if (slowResult.kind === "PhaseOutput") {
368
912
  const coordKey = instance.coordinate.join("/");
369
913
  resolvedData.push({
@@ -375,7 +919,7 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
375
919
  carryForwards[coordKey] = slowResult.carryForward;
376
920
  discoveredForFast.push({
377
921
  contractName: instance.contractName,
378
- props: instance.props,
922
+ props: normalizedProps,
379
923
  coordinate: instance.coordinate
380
924
  });
381
925
  }
@@ -389,6 +933,22 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
389
933
  instancePhaseData: { discovered: discoveredForFast, carryForwards }
390
934
  };
391
935
  }
936
+ function validateForEachInstances(forEachInstances, headlessInstanceComponents) {
937
+ const componentByContractName = /* @__PURE__ */ new Map();
938
+ for (const comp of headlessInstanceComponents) {
939
+ componentByContractName.set(comp.contractName, comp);
940
+ }
941
+ const validations = [];
942
+ for (const instance of forEachInstances) {
943
+ const comp = componentByContractName.get(instance.contractName);
944
+ if (comp?.compDefinition.slowlyRender) {
945
+ validations.push(
946
+ `<jay:${instance.contractName}> inside forEach has a slow rendering phase. Headless components with slow phases cannot be used inside forEach because forEach items are only known at request time, after slow rendering completes. Use slowForEach instead, or remove the slow phase from the component.`
947
+ );
948
+ }
949
+ }
950
+ return validations;
951
+ }
392
952
  class ActionRegistry {
393
953
  constructor() {
394
954
  __publicField(this, "actions", /* @__PURE__ */ new Map());
@@ -429,10 +989,38 @@ class ActionRegistry {
429
989
  /**
430
990
  * Gets all registered action names.
431
991
  *
432
- * @returns Array of registered action names
992
+ * @returns Array of registered action names
993
+ */
994
+ getNames() {
995
+ return Array.from(this.actions.keys());
996
+ }
997
+ /**
998
+ * Attaches metadata from a .jay-action file to a registered action.
999
+ * Called during action discovery when a plugin declares action metadata.
1000
+ *
1001
+ * @param actionName - The action name
1002
+ * @param metadata - Parsed ActionMetadata from .jay-action file
1003
+ */
1004
+ setMetadata(actionName, metadata) {
1005
+ const action = this.actions.get(actionName);
1006
+ if (action) {
1007
+ action.metadata = metadata;
1008
+ }
1009
+ }
1010
+ /**
1011
+ * Gets all registered actions that have .jay-action metadata.
1012
+ * These are the actions that should be exposed to AI agents.
1013
+ *
1014
+ * @returns Array of { actionName, metadata } for actions with metadata
433
1015
  */
434
- getNames() {
435
- return Array.from(this.actions.keys());
1016
+ getActionsWithMetadata() {
1017
+ const result = [];
1018
+ for (const action of this.actions.values()) {
1019
+ if (action.metadata) {
1020
+ result.push({ actionName: action.actionName, metadata: action.metadata });
1021
+ }
1022
+ }
1023
+ return result;
436
1024
  }
437
1025
  /**
438
1026
  * Clears all registered actions.
@@ -536,102 +1124,62 @@ async function executeAction(actionName, input) {
536
1124
  function getActionCacheHeaders(actionName) {
537
1125
  return actionRegistry.getCacheHeaders(actionName);
538
1126
  }
539
- var __defProp2 = Object.defineProperty;
540
- var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
541
- var __publicField2 = (obj, key, value) => {
542
- __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
543
- return value;
544
- };
545
- class JayAtomicType {
546
- constructor(name) {
547
- __publicField2(this, "kind", 0);
548
- this.name = name;
549
- }
550
- }
551
- new JayAtomicType("string");
552
- new JayAtomicType("number");
553
- new JayAtomicType("boolean");
554
- new JayAtomicType("Date");
555
- new JayAtomicType("Unknown");
556
- class JayObjectType {
557
- constructor(name, props) {
558
- __publicField2(this, "kind", 8);
559
- this.name = name;
560
- this.props = props;
561
- }
562
- }
563
- new JayObjectType("Error", {
564
- message: new JayAtomicType("string"),
565
- name: new JayAtomicType("string"),
566
- stack: new JayAtomicType("string")
567
- });
568
- var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
569
- RuntimeMode2["MainTrusted"] = "mainTrusted";
570
- RuntimeMode2["MainSandbox"] = "mainSandbox";
571
- RuntimeMode2["WorkerTrusted"] = "workerTrusted";
572
- RuntimeMode2["WorkerSandbox"] = "workerSandbox";
573
- return RuntimeMode2;
574
- })(RuntimeMode || {});
575
- const TS_EXTENSION = ".ts";
576
- const JAY_QUERY_PREFIX = "?jay-";
577
- [
578
- // Build environments
579
- {
580
- pattern: `${JAY_QUERY_PREFIX}${"client"}`,
581
- buildEnv: "client"
582
- /* Client */
583
- },
584
- {
585
- pattern: `${JAY_QUERY_PREFIX}${"server"}`,
586
- buildEnv: "server"
587
- /* Server */
588
- },
589
- // Runtime modes (with .ts suffix)
590
- {
591
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
592
- runtimeMode: RuntimeMode.MainSandbox
593
- },
594
- {
595
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
596
- runtimeMode: RuntimeMode.WorkerTrusted
597
- },
598
- {
599
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
600
- runtimeMode: RuntimeMode.WorkerSandbox
601
- },
602
- // Runtime modes (without .ts suffix)
603
- {
604
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
605
- runtimeMode: RuntimeMode.MainSandbox
606
- },
607
- {
608
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
609
- runtimeMode: RuntimeMode.WorkerTrusted
610
- },
611
- {
612
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
613
- runtimeMode: RuntimeMode.WorkerSandbox
614
- }
615
- ];
616
- createRequire(import.meta.url);
617
- function loadPluginManifest(pluginDir) {
618
- const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
619
- if (!fs$2.existsSync(pluginYamlPath)) {
620
- return null;
621
- }
1127
+ function parseActionMetadata(yamlContent, fileName) {
622
1128
  try {
623
- const yamlContent = fs$2.readFileSync(pluginYamlPath, "utf-8");
624
- return YAML.parse(yamlContent);
1129
+ const parsed = parseAction(yamlContent, fileName);
1130
+ if (parsed.validations.length > 0) {
1131
+ getLogger().warn(
1132
+ `[ActionMetadata] ${fileName}: validation errors: ${parsed.validations.join(", ")}`
1133
+ );
1134
+ return null;
1135
+ }
1136
+ if (!parsed.val) {
1137
+ getLogger().warn(`[ActionMetadata] ${fileName}: parsing returned no result`);
1138
+ return null;
1139
+ }
1140
+ const action = parsed.val;
1141
+ const inputJsonSchema = jayTypeToJsonSchema(action.inputType);
1142
+ let inputSchema;
1143
+ if (inputJsonSchema && inputJsonSchema.type === "object") {
1144
+ inputSchema = {
1145
+ type: "object",
1146
+ properties: inputJsonSchema.properties || {},
1147
+ ...inputJsonSchema.required && inputJsonSchema.required.length > 0 && { required: inputJsonSchema.required }
1148
+ };
1149
+ } else {
1150
+ inputSchema = { type: "object", properties: {} };
1151
+ }
1152
+ const metadata = {
1153
+ name: action.name,
1154
+ description: action.description,
1155
+ inputSchema
1156
+ };
1157
+ if (action.outputType) {
1158
+ const outputJsonSchema = jayTypeToJsonSchema(action.outputType);
1159
+ if (outputJsonSchema) {
1160
+ metadata.outputSchema = outputJsonSchema;
1161
+ }
1162
+ }
1163
+ return metadata;
625
1164
  } catch (error) {
1165
+ getLogger().error(
1166
+ `[ActionMetadata] Failed to parse ${fileName}: ${error instanceof Error ? error.message : error}`
1167
+ );
626
1168
  return null;
627
1169
  }
628
1170
  }
629
- const s = createRequire(import.meta.url), e = s("typescript");
630
- new Proxy(e, {
631
- get(t, r) {
632
- return t[r];
1171
+ function loadActionMetadata(filePath) {
1172
+ if (!fs.existsSync(filePath)) {
1173
+ getLogger().warn(`[ActionMetadata] File not found: ${filePath}`);
1174
+ return null;
633
1175
  }
634
- });
1176
+ const yamlContent = fs.readFileSync(filePath, "utf-8");
1177
+ const fileName = path.basename(filePath);
1178
+ return parseActionMetadata(yamlContent, fileName);
1179
+ }
1180
+ function resolveActionMetadataPath(actionPath, pluginDir) {
1181
+ return path.resolve(pluginDir, actionPath);
1182
+ }
635
1183
  const require$2 = createRequire$1(import.meta.url);
636
1184
  async function discoverAndRegisterActions(options) {
637
1185
  const {
@@ -753,6 +1301,7 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
753
1301
  const actions = await registerNpmPluginActions(
754
1302
  packageName,
755
1303
  pluginConfig,
1304
+ pluginDir,
756
1305
  registry,
757
1306
  verbose,
758
1307
  viteServer
@@ -776,7 +1325,25 @@ function tryResolvePluginYaml(packageName, projectRoot) {
776
1325
  return null;
777
1326
  }
778
1327
  }
779
- async function registerNpmPluginActions(packageName, pluginConfig, registry, verbose, viteServer) {
1328
+ function resolveNpmActionMetadataPath(actionPath, packageName, pluginDir) {
1329
+ if (!actionPath.startsWith(".")) {
1330
+ try {
1331
+ return require$2.resolve(`${packageName}/${actionPath}`, {
1332
+ paths: [pluginDir]
1333
+ });
1334
+ } catch {
1335
+ }
1336
+ }
1337
+ const resolved = resolveActionMetadataPath(actionPath, pluginDir);
1338
+ if (fs.existsSync(resolved)) {
1339
+ return resolved;
1340
+ }
1341
+ getLogger().warn(
1342
+ `[Actions] Could not resolve .jay-action file "${actionPath}" for package "${packageName}"`
1343
+ );
1344
+ return null;
1345
+ }
1346
+ async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, registry, verbose, viteServer) {
780
1347
  const registeredActions = [];
781
1348
  try {
782
1349
  let pluginModule;
@@ -785,15 +1352,33 @@ async function registerNpmPluginActions(packageName, pluginConfig, registry, ver
785
1352
  } else {
786
1353
  pluginModule = await import(packageName);
787
1354
  }
788
- for (const actionName of pluginConfig.actions) {
1355
+ for (const entry of pluginConfig.actions) {
1356
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
789
1357
  const actionExport = pluginModule[actionName];
790
1358
  if (actionExport && isJayAction(actionExport)) {
791
1359
  registry.register(actionExport);
792
- registeredActions.push(actionExport.actionName);
793
- if (verbose) {
794
- getLogger().info(
795
- `[Actions] Registered NPM plugin action: ${actionExport.actionName}`
1360
+ const registeredName = actionExport.actionName;
1361
+ registeredActions.push(registeredName);
1362
+ if (actionPath) {
1363
+ const metadataFilePath = resolveNpmActionMetadataPath(
1364
+ actionPath,
1365
+ packageName,
1366
+ pluginDir
796
1367
  );
1368
+ if (metadataFilePath) {
1369
+ const metadata = loadActionMetadata(metadataFilePath);
1370
+ if (metadata) {
1371
+ registry.setMetadata(registeredName, metadata);
1372
+ if (verbose) {
1373
+ getLogger().info(
1374
+ `[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
1375
+ );
1376
+ }
1377
+ }
1378
+ }
1379
+ }
1380
+ if (verbose) {
1381
+ getLogger().info(`[Actions] Registered NPM plugin action: ${registeredName}`);
797
1382
  }
798
1383
  } else {
799
1384
  getLogger().warn(
@@ -841,15 +1426,27 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
841
1426
  } else {
842
1427
  pluginModule = await import(modulePath);
843
1428
  }
844
- for (const actionName of pluginConfig.actions) {
1429
+ for (const entry of pluginConfig.actions) {
1430
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
845
1431
  const actionExport = pluginModule[actionName];
846
1432
  if (actionExport && isJayAction(actionExport)) {
847
1433
  registry.register(actionExport);
848
- registeredActions.push(actionExport.actionName);
1434
+ const registeredName = actionExport.actionName;
1435
+ registeredActions.push(registeredName);
1436
+ if (actionPath) {
1437
+ const metadataFilePath = resolveActionMetadataPath(actionPath, pluginPath);
1438
+ const metadata = loadActionMetadata(metadataFilePath);
1439
+ if (metadata) {
1440
+ registry.setMetadata(registeredName, metadata);
1441
+ if (verbose) {
1442
+ getLogger().info(
1443
+ `[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
1444
+ );
1445
+ }
1446
+ }
1447
+ }
849
1448
  if (verbose) {
850
- getLogger().info(
851
- `[Actions] Registered plugin action: ${actionExport.actionName}`
852
- );
1449
+ getLogger().info(`[Actions] Registered plugin action: ${registeredName}`);
853
1450
  }
854
1451
  } else {
855
1452
  getLogger().warn(
@@ -999,7 +1596,8 @@ async function discoverPluginsWithInit(options) {
999
1596
  isLocal: scanned.isLocal,
1000
1597
  initModule: initConfig.module,
1001
1598
  initExport: initConfig.export,
1002
- dependencies: scanned.dependencies
1599
+ dependencies: scanned.dependencies,
1600
+ global: scanned.manifest.global === true
1003
1601
  });
1004
1602
  if (verbose) {
1005
1603
  getLogger().info(`[PluginInit] Found plugin with init: ${scanned.name}`);
@@ -1073,6 +1671,7 @@ function sortPluginsByDependencies(plugins) {
1073
1671
  return sorted;
1074
1672
  }
1075
1673
  async function executePluginServerInits(plugins, viteServer, verbose = false) {
1674
+ const initErrors = /* @__PURE__ */ new Map();
1076
1675
  for (const plugin of plugins) {
1077
1676
  try {
1078
1677
  let modulePath;
@@ -1106,11 +1705,14 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
1106
1705
  }
1107
1706
  }
1108
1707
  } catch (error) {
1708
+ const err = error instanceof Error ? error : new Error(String(error));
1709
+ initErrors.set(plugin.name, err);
1109
1710
  getLogger().error(
1110
1711
  `[PluginInit] Failed to execute server init for "${plugin.name}": ${error}`
1111
1712
  );
1112
1713
  }
1113
1714
  }
1715
+ return initErrors;
1114
1716
  }
1115
1717
  function preparePluginClientInits(plugins) {
1116
1718
  return plugins.map((plugin) => {
@@ -1129,16 +1731,8 @@ function preparePluginClientInits(plugins) {
1129
1731
  };
1130
1732
  });
1131
1733
  }
1132
- function makeCacheKey(jayHtmlPath, params) {
1133
- const sortedParams = Object.keys(params).sort().reduce(
1134
- (acc, key) => {
1135
- acc[key] = params[key];
1136
- return acc;
1137
- },
1138
- {}
1139
- );
1140
- return `${jayHtmlPath}:${JSON.stringify(sortedParams)}`;
1141
- }
1734
+ const CACHE_TAG_START = '<script type="application/jay-cache">';
1735
+ const CACHE_TAG_END = "<\/script>";
1142
1736
  function hashParams(params) {
1143
1737
  const sortedParams = Object.keys(params).sort().reduce(
1144
1738
  (acc, key) => {
@@ -1152,81 +1746,126 @@ function hashParams(params) {
1152
1746
  return "";
1153
1747
  return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
1154
1748
  }
1749
+ function embedCacheMetadata(jayHtmlContent, slowViewState, carryForward, sourcePath) {
1750
+ const metadata = JSON.stringify({ slowViewState, carryForward, sourcePath });
1751
+ const cacheTag = `${CACHE_TAG_START}${metadata}${CACHE_TAG_END}`;
1752
+ const headMatch = jayHtmlContent.match(/<head[^>]*>/i);
1753
+ if (headMatch) {
1754
+ const insertPos = headMatch.index + headMatch[0].length;
1755
+ return jayHtmlContent.substring(0, insertPos) + "\n" + cacheTag + jayHtmlContent.substring(insertPos);
1756
+ }
1757
+ return `${cacheTag}
1758
+ ${jayHtmlContent}`;
1759
+ }
1760
+ function extractCacheMetadata(fileContent) {
1761
+ const startIdx = fileContent.indexOf(CACHE_TAG_START);
1762
+ if (startIdx === -1)
1763
+ return void 0;
1764
+ const jsonStart = startIdx + CACHE_TAG_START.length;
1765
+ const endIdx = fileContent.indexOf(CACHE_TAG_END, jsonStart);
1766
+ if (endIdx === -1)
1767
+ return void 0;
1768
+ const jsonStr = fileContent.substring(jsonStart, endIdx);
1769
+ const metadata = JSON.parse(jsonStr);
1770
+ const tagEnd = endIdx + CACHE_TAG_END.length;
1771
+ const afterTag = fileContent[tagEnd] === "\n" ? tagEnd + 1 : tagEnd;
1772
+ const content = fileContent.substring(0, startIdx) + fileContent.substring(afterTag);
1773
+ return {
1774
+ content,
1775
+ slowViewState: metadata.slowViewState,
1776
+ carryForward: metadata.carryForward,
1777
+ sourcePath: metadata.sourcePath
1778
+ };
1779
+ }
1155
1780
  class SlowRenderCache {
1156
1781
  /**
1157
1782
  * @param cacheDir - Directory where pre-rendered jay-html files are stored
1158
1783
  * @param pagesRoot - Root directory of the pages (for relative path calculation)
1159
1784
  */
1160
1785
  constructor(cacheDir, pagesRoot) {
1161
- __publicField(this, "cache", /* @__PURE__ */ new Map());
1162
- __publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
1786
+ /** Maps source jay-html path set of pre-rendered file paths (for invalidation) */
1787
+ __publicField(this, "pathToFiles", /* @__PURE__ */ new Map());
1163
1788
  __publicField(this, "cacheDir");
1164
1789
  __publicField(this, "pagesRoot");
1165
1790
  this.cacheDir = cacheDir;
1166
1791
  this.pagesRoot = pagesRoot;
1167
1792
  }
1168
1793
  /**
1169
- * Get a cached pre-rendered jay-html entry
1794
+ * Get a cached pre-rendered jay-html entry by reading from disk.
1795
+ * Returns undefined if the cache file doesn't exist or has no metadata tag.
1170
1796
  */
1171
- get(jayHtmlPath, params) {
1172
- const key = makeCacheKey(jayHtmlPath, params);
1173
- return this.cache.get(key);
1797
+ async get(jayHtmlPath, params) {
1798
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1799
+ let fileContent;
1800
+ try {
1801
+ fileContent = await fs$2.readFile(preRenderedPath, "utf-8");
1802
+ } catch {
1803
+ return void 0;
1804
+ }
1805
+ const extracted = extractCacheMetadata(fileContent);
1806
+ if (!extracted)
1807
+ return void 0;
1808
+ this.trackFile(jayHtmlPath, preRenderedPath);
1809
+ return {
1810
+ preRenderedPath,
1811
+ preRenderedContent: extracted.content,
1812
+ slowViewState: extracted.slowViewState,
1813
+ carryForward: extracted.carryForward,
1814
+ sourcePath: extracted.sourcePath
1815
+ };
1174
1816
  }
1175
1817
  /**
1176
- * Store a pre-rendered jay-html entry in the cache.
1177
- * Writes the pre-rendered content to disk and stores metadata in memory.
1818
+ * Store a pre-rendered jay-html entry.
1819
+ * Embeds metadata as a <script> tag and writes to disk.
1820
+ * Returns the full cache entry with stripped content.
1178
1821
  */
1179
1822
  async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward) {
1180
- const key = makeCacheKey(jayHtmlPath, params);
1181
- const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1182
- const dir = path__default.dirname(relativePath);
1183
- const basename = path__default.basename(relativePath, ".jay-html");
1184
- const paramsHash = hashParams(params);
1185
- const cacheFileName = `${basename}${paramsHash}.jay-html`;
1186
- const preRenderedPath = path__default.join(this.cacheDir, dir, cacheFileName);
1187
- await fs$1.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1188
- await fs$1.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
1189
- if (!this.pathToKeys.has(jayHtmlPath)) {
1190
- this.pathToKeys.set(jayHtmlPath, /* @__PURE__ */ new Set());
1191
- }
1192
- this.pathToKeys.get(jayHtmlPath).add(key);
1193
- const entry = {
1823
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1824
+ const fileContent = embedCacheMetadata(
1825
+ preRenderedJayHtml,
1826
+ slowViewState,
1827
+ carryForward,
1828
+ jayHtmlPath
1829
+ );
1830
+ await fs$2.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1831
+ await fs$2.writeFile(preRenderedPath, fileContent, "utf-8");
1832
+ this.trackFile(jayHtmlPath, preRenderedPath);
1833
+ return {
1194
1834
  preRenderedPath,
1835
+ preRenderedContent: preRenderedJayHtml,
1195
1836
  slowViewState,
1196
1837
  carryForward,
1197
- createdAt: Date.now(),
1198
1838
  sourcePath: jayHtmlPath
1199
1839
  };
1200
- this.cache.set(key, entry);
1201
- return preRenderedPath;
1202
1840
  }
1203
1841
  /**
1204
1842
  * Check if a pre-rendered entry exists for the given path and params
1205
1843
  */
1206
- has(jayHtmlPath, params) {
1207
- const key = makeCacheKey(jayHtmlPath, params);
1208
- return this.cache.has(key);
1844
+ async has(jayHtmlPath, params) {
1845
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1846
+ try {
1847
+ await fs$2.access(preRenderedPath);
1848
+ return true;
1849
+ } catch {
1850
+ return false;
1851
+ }
1209
1852
  }
1210
1853
  /**
1211
1854
  * Invalidate all cached entries for a given jay-html source path.
1212
- * This is called when the source file changes.
1213
- * Also deletes the cached files from disk.
1855
+ * Deletes cached files from disk.
1214
1856
  */
1215
1857
  async invalidate(jayHtmlPath) {
1216
- const keys = this.pathToKeys.get(jayHtmlPath);
1217
- if (keys) {
1218
- for (const key of keys) {
1219
- const entry = this.cache.get(key);
1220
- if (entry) {
1221
- try {
1222
- await fs$1.unlink(entry.preRenderedPath);
1223
- } catch {
1224
- }
1858
+ const files = this.pathToFiles.get(jayHtmlPath);
1859
+ if (files) {
1860
+ for (const filePath of files) {
1861
+ try {
1862
+ await fs$2.unlink(filePath);
1863
+ } catch {
1225
1864
  }
1226
- this.cache.delete(key);
1227
1865
  }
1228
- this.pathToKeys.delete(jayHtmlPath);
1866
+ this.pathToFiles.delete(jayHtmlPath);
1229
1867
  }
1868
+ await this.scanAndDeleteCacheFiles(jayHtmlPath);
1230
1869
  }
1231
1870
  /**
1232
1871
  * Invalidate all entries that depend on a changed file.
@@ -1254,26 +1893,67 @@ class SlowRenderCache {
1254
1893
  * Clear all cached entries and delete cached files from disk
1255
1894
  */
1256
1895
  async clear() {
1257
- for (const entry of this.cache.values()) {
1258
- try {
1259
- await fs$1.unlink(entry.preRenderedPath);
1260
- } catch {
1896
+ for (const files of this.pathToFiles.values()) {
1897
+ for (const filePath of files) {
1898
+ try {
1899
+ await fs$2.unlink(filePath);
1900
+ } catch {
1901
+ }
1261
1902
  }
1262
1903
  }
1263
- this.cache.clear();
1264
- this.pathToKeys.clear();
1904
+ this.pathToFiles.clear();
1905
+ try {
1906
+ await fs$2.rm(this.cacheDir, { recursive: true, force: true });
1907
+ } catch {
1908
+ }
1265
1909
  }
1266
1910
  /**
1267
- * Get the number of cached entries
1911
+ * Get all cached jay-html paths (for debugging/monitoring)
1268
1912
  */
1269
- get size() {
1270
- return this.cache.size;
1913
+ getCachedPaths() {
1914
+ return Array.from(this.pathToFiles.keys());
1271
1915
  }
1272
1916
  /**
1273
- * Get all cached jay-html paths (for debugging/monitoring)
1917
+ * Compute the cache file path for a given jay-html path and params.
1274
1918
  */
1275
- getCachedPaths() {
1276
- return Array.from(this.pathToKeys.keys());
1919
+ computeCachePath(jayHtmlPath, params) {
1920
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1921
+ const dir = path__default.dirname(relativePath);
1922
+ const basename = path__default.basename(relativePath, ".jay-html");
1923
+ const paramsHash = hashParams(params);
1924
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
1925
+ return path__default.join(this.cacheDir, dir, cacheFileName);
1926
+ }
1927
+ /**
1928
+ * Track a pre-rendered file path for invalidation.
1929
+ */
1930
+ trackFile(jayHtmlPath, preRenderedPath) {
1931
+ if (!this.pathToFiles.has(jayHtmlPath)) {
1932
+ this.pathToFiles.set(jayHtmlPath, /* @__PURE__ */ new Set());
1933
+ }
1934
+ this.pathToFiles.get(jayHtmlPath).add(preRenderedPath);
1935
+ }
1936
+ /**
1937
+ * Scan the cache directory for files matching a route and delete them.
1938
+ * Handles the startup case where pathToFiles is not populated from a previous session.
1939
+ */
1940
+ async scanAndDeleteCacheFiles(jayHtmlPath) {
1941
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1942
+ const dir = path__default.dirname(relativePath);
1943
+ const basename = path__default.basename(relativePath, ".jay-html");
1944
+ const cacheSubDir = path__default.join(this.cacheDir, dir);
1945
+ try {
1946
+ const files = await fs$2.readdir(cacheSubDir);
1947
+ for (const file of files) {
1948
+ if (file.startsWith(basename) && file.endsWith(".jay-html")) {
1949
+ try {
1950
+ await fs$2.unlink(path__default.join(cacheSubDir, file));
1951
+ } catch {
1952
+ }
1953
+ }
1954
+ }
1955
+ } catch {
1956
+ }
1277
1957
  }
1278
1958
  }
1279
1959
  const require2 = createRequire(import.meta.url);
@@ -1361,8 +2041,7 @@ async function executeDynamicGenerator(plugin, config, projectRoot, services, ve
1361
2041
  if (verbose) {
1362
2042
  getLogger().info(` Executing generator...`);
1363
2043
  }
1364
- const result = await generator.generate(...resolvedServices);
1365
- return result;
2044
+ return await generator.generate(...resolvedServices);
1366
2045
  }
1367
2046
  function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
1368
2047
  const { pluginPath, isLocal, packageName } = plugin;
@@ -1384,6 +2063,25 @@ function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
1384
2063
  return path.join(pluginPath, contractSpec);
1385
2064
  }
1386
2065
  }
2066
+ function resolveActionFilePath(actionPath, packageName, pluginPath, isLocal, projectRoot) {
2067
+ if (!isLocal && !actionPath.startsWith(".")) {
2068
+ try {
2069
+ return require2.resolve(`${packageName}/${actionPath}`, {
2070
+ paths: [projectRoot]
2071
+ });
2072
+ } catch {
2073
+ const possiblePaths = [
2074
+ path.join(pluginPath, "dist", actionPath),
2075
+ path.join(pluginPath, "lib", actionPath),
2076
+ path.join(pluginPath, actionPath)
2077
+ ];
2078
+ const found = possiblePaths.find((p) => fs.existsSync(p));
2079
+ return found || null;
2080
+ }
2081
+ }
2082
+ const resolved = resolveActionMetadataPath(actionPath, pluginPath);
2083
+ return fs.existsSync(resolved) ? resolved : null;
2084
+ }
1387
2085
  function toKebabCase(str) {
1388
2086
  return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
1389
2087
  }
@@ -1399,13 +2097,12 @@ function getJayStackVersion() {
1399
2097
  async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
1400
2098
  const {
1401
2099
  projectRoot,
1402
- outputDir = path.join(projectRoot, "build", "materialized-contracts"),
2100
+ outputDir = path.join(projectRoot, "agent-kit", "materialized-contracts"),
1403
2101
  dynamicOnly = false,
1404
2102
  pluginFilter,
1405
2103
  verbose = false,
1406
2104
  viteServer
1407
2105
  } = options;
1408
- const contracts = [];
1409
2106
  const pluginsIndexMap = /* @__PURE__ */ new Map();
1410
2107
  let staticCount = 0;
1411
2108
  let dynamicCount = 0;
@@ -1429,6 +2126,14 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1429
2126
  📦 Processing plugin: ${plugin.name}`);
1430
2127
  }
1431
2128
  const { manifest } = plugin;
2129
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
2130
+ if (!pluginsIndexMap.has(plugin.name)) {
2131
+ pluginsIndexMap.set(plugin.name, {
2132
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
2133
+ contracts: [],
2134
+ actions: []
2135
+ });
2136
+ }
1432
2137
  if (!dynamicOnly && manifest.contracts) {
1433
2138
  for (const contract of manifest.contracts) {
1434
2139
  const contractPath = resolveStaticContractPath(
@@ -1437,26 +2142,12 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1437
2142
  projectRoot
1438
2143
  );
1439
2144
  const relativePath = path.relative(projectRoot, contractPath);
1440
- const entry = {
1441
- plugin: plugin.name,
1442
- name: contract.name,
1443
- type: "static",
1444
- path: "./" + relativePath
1445
- };
1446
- contracts.push(entry);
1447
- staticCount++;
1448
- const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1449
- if (!pluginsIndexMap.has(plugin.name)) {
1450
- pluginsIndexMap.set(plugin.name, {
1451
- path: "./" + pluginRelPath.replace(/\\/g, "/"),
1452
- contracts: []
1453
- });
1454
- }
1455
2145
  pluginsIndexMap.get(plugin.name).contracts.push({
1456
2146
  name: contract.name,
1457
2147
  type: "static",
1458
2148
  path: "./" + relativePath
1459
2149
  });
2150
+ staticCount++;
1460
2151
  if (verbose) {
1461
2152
  getLogger().info(` 📄 Static: ${contract.name}`);
1462
2153
  }
@@ -1486,27 +2177,14 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1486
2177
  const filePath = path.join(pluginOutputDir, fileName);
1487
2178
  fs.writeFileSync(filePath, generated.yaml, "utf-8");
1488
2179
  const relativePath = path.relative(projectRoot, filePath);
1489
- const dynEntry = {
1490
- plugin: plugin.name,
2180
+ const contractEntry = {
1491
2181
  name: fullName,
1492
2182
  type: "dynamic",
1493
2183
  path: "./" + relativePath,
1494
- metadata: generated.metadata
2184
+ ...generated.metadata && { metadata: generated.metadata }
1495
2185
  };
1496
- contracts.push(dynEntry);
2186
+ pluginsIndexMap.get(plugin.name).contracts.push(contractEntry);
1497
2187
  dynamicCount++;
1498
- const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1499
- if (!pluginsIndexMap.has(plugin.name)) {
1500
- pluginsIndexMap.set(plugin.name, {
1501
- path: "./" + pluginRelPath.replace(/\\/g, "/"),
1502
- contracts: []
1503
- });
1504
- }
1505
- pluginsIndexMap.get(plugin.name).contracts.push({
1506
- name: fullName,
1507
- type: "dynamic",
1508
- path: "./" + relativePath
1509
- });
1510
2188
  if (verbose) {
1511
2189
  getLogger().info(` ⚡ Materialized: ${fullName}`);
1512
2190
  }
@@ -1518,33 +2196,54 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1518
2196
  }
1519
2197
  }
1520
2198
  }
2199
+ if (manifest.actions && Array.isArray(manifest.actions)) {
2200
+ for (const entry of manifest.actions) {
2201
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
2202
+ if (!actionPath)
2203
+ continue;
2204
+ const metadataFilePath = resolveActionFilePath(
2205
+ actionPath,
2206
+ plugin.packageName,
2207
+ plugin.pluginPath,
2208
+ plugin.isLocal,
2209
+ projectRoot
2210
+ );
2211
+ if (!metadataFilePath)
2212
+ continue;
2213
+ const metadata = loadActionMetadata(metadataFilePath);
2214
+ if (!metadata)
2215
+ continue;
2216
+ const actionRelPath = path.relative(projectRoot, metadataFilePath);
2217
+ pluginsIndexMap.get(plugin.name).actions.push({
2218
+ name: metadata.name,
2219
+ description: metadata.description,
2220
+ path: "./" + actionRelPath.replace(/\\/g, "/")
2221
+ });
2222
+ if (verbose) {
2223
+ getLogger().info(` 🔧 Action: ${metadata.name} (${actionPath})`);
2224
+ }
2225
+ }
2226
+ }
1521
2227
  }
1522
- const index = {
1523
- materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1524
- jay_stack_version: getJayStackVersion(),
1525
- contracts
1526
- };
1527
- fs.mkdirSync(outputDir, { recursive: true });
1528
- const indexPath = path.join(outputDir, "contracts-index.yaml");
1529
- fs.writeFileSync(indexPath, YAML.stringify(index), "utf-8");
1530
2228
  const pluginsIndex = {
1531
- materialized_at: index.materialized_at,
1532
- jay_stack_version: index.jay_stack_version,
2229
+ jay_stack_version: getJayStackVersion(),
1533
2230
  plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
1534
2231
  name,
1535
2232
  path: data.path,
1536
- contracts: data.contracts
2233
+ contracts: data.contracts,
2234
+ ...data.actions.length > 0 && { actions: data.actions }
1537
2235
  }))
1538
2236
  };
1539
- const pluginsIndexPath = path.join(outputDir, "plugins-index.yaml");
2237
+ fs.mkdirSync(outputDir, { recursive: true });
2238
+ const agentKitDir = path.dirname(outputDir);
2239
+ const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
1540
2240
  fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
1541
2241
  if (verbose) {
1542
2242
  getLogger().info(`
1543
- Contracts index written to: ${indexPath}`);
1544
- getLogger().info(`✅ Plugins index written to: ${pluginsIndexPath}`);
2243
+ Plugins index written to: ${pluginsIndexPath}`);
1545
2244
  }
1546
2245
  return {
1547
- index,
2246
+ pluginsIndex,
1548
2247
  staticCount,
1549
2248
  dynamicCount,
1550
2249
  outputDir
@@ -1552,7 +2251,7 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1552
2251
  }
1553
2252
  async function listContracts(options) {
1554
2253
  const { projectRoot, dynamicOnly = false, pluginFilter } = options;
1555
- const contracts = [];
2254
+ const pluginsMap = /* @__PURE__ */ new Map();
1556
2255
  const plugins = await scanPlugins({
1557
2256
  projectRoot,
1558
2257
  includeDevDeps: true
@@ -1561,6 +2260,13 @@ async function listContracts(options) {
1561
2260
  if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1562
2261
  continue;
1563
2262
  const { manifest } = plugin;
2263
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
2264
+ if (!pluginsMap.has(plugin.name)) {
2265
+ pluginsMap.set(plugin.name, {
2266
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
2267
+ contracts: []
2268
+ });
2269
+ }
1564
2270
  if (!dynamicOnly && manifest.contracts) {
1565
2271
  for (const contract of manifest.contracts) {
1566
2272
  const contractPath = resolveStaticContractPath(
@@ -1569,8 +2275,7 @@ async function listContracts(options) {
1569
2275
  projectRoot
1570
2276
  );
1571
2277
  const relativePath = path.relative(projectRoot, contractPath);
1572
- contracts.push({
1573
- plugin: plugin.name,
2278
+ pluginsMap.get(plugin.name).contracts.push({
1574
2279
  name: contract.name,
1575
2280
  type: "static",
1576
2281
  path: "./" + relativePath
@@ -1580,8 +2285,7 @@ async function listContracts(options) {
1580
2285
  if (manifest.dynamic_contracts) {
1581
2286
  const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1582
2287
  for (const config of dynamicConfigs) {
1583
- contracts.push({
1584
- plugin: plugin.name,
2288
+ pluginsMap.get(plugin.name).contracts.push({
1585
2289
  name: `${config.prefix}/*`,
1586
2290
  type: "dynamic",
1587
2291
  path: "(run materialization to generate)"
@@ -1590,9 +2294,12 @@ async function listContracts(options) {
1590
2294
  }
1591
2295
  }
1592
2296
  return {
1593
- materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1594
2297
  jay_stack_version: getJayStackVersion(),
1595
- contracts
2298
+ plugins: Array.from(pluginsMap.entries()).map(([name, data]) => ({
2299
+ name,
2300
+ path: data.path,
2301
+ contracts: data.contracts
2302
+ }))
1596
2303
  };
1597
2304
  }
1598
2305
  async function discoverPluginsWithSetup(options) {
@@ -1673,13 +2380,14 @@ async function executePluginSetup(plugin, options) {
1673
2380
  return handler(context);
1674
2381
  }
1675
2382
  async function executePluginReferences(plugin, options) {
1676
- const { projectRoot, force, viteServer } = options;
2383
+ const { projectRoot, force, initError, viteServer } = options;
1677
2384
  const referencesDir = path.join(projectRoot, "agent-kit", "references", plugin.name);
1678
2385
  const context = {
1679
2386
  pluginName: plugin.name,
1680
2387
  projectRoot,
1681
2388
  referencesDir,
1682
2389
  services: getServiceRegistry(),
2390
+ initError,
1683
2391
  force
1684
2392
  };
1685
2393
  const handler = await loadHandler(
@@ -1726,9 +2434,12 @@ export {
1726
2434
  DevSlowlyChangingPhase,
1727
2435
  SlowRenderCache,
1728
2436
  actionRegistry,
2437
+ buildAutomationWrap,
2438
+ buildScriptFragments,
1729
2439
  clearActionRegistry,
1730
2440
  clearClientInitData,
1731
2441
  clearLifecycleCallbacks,
2442
+ clearServerElementCache,
1732
2443
  clearServiceRegistry,
1733
2444
  discoverAllPluginActions,
1734
2445
  discoverAndRegisterActions,
@@ -1741,6 +2452,7 @@ export {
1741
2452
  executePluginServerInits,
1742
2453
  executePluginSetup,
1743
2454
  generateClientScript,
2455
+ generateSSRPageHtml,
1744
2456
  getActionCacheHeaders,
1745
2457
  getClientInitData,
1746
2458
  getClientInitDataForKey,
@@ -1750,15 +2462,19 @@ export {
1750
2462
  getServiceRegistry,
1751
2463
  hasAction,
1752
2464
  hasService,
2465
+ invalidateServerElementCache,
1753
2466
  listContracts,
2467
+ loadActionMetadata,
1754
2468
  loadPageParts,
1755
2469
  materializeContracts,
1756
2470
  onInit,
1757
2471
  onShutdown,
2472
+ parseActionMetadata,
1758
2473
  preparePluginClientInits,
1759
2474
  registerAction,
1760
2475
  registerService,
1761
2476
  renderFastChangingData,
2477
+ resolveActionMetadataPath,
1762
2478
  resolveServices,
1763
2479
  runInitCallbacks,
1764
2480
  runLoadParams,
@@ -1767,5 +2483,6 @@ export {
1767
2483
  scanPlugins,
1768
2484
  setClientInitData,
1769
2485
  slowRenderInstances,
1770
- sortPluginsByDependencies
2486
+ sortPluginsByDependencies,
2487
+ validateForEachInstances
1771
2488
  };