@avocadostudio-ai/orchestrator-core 0.6.0 → 0.8.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/http/publish-actions.d.ts +1 -1
- package/dist/http/publish-actions.js +28 -9
- package/dist/ops/ops-engine.js +45 -1
- package/package.json +19 -20
|
@@ -68,7 +68,8 @@ async function readPublishedJsonFile() {
|
|
|
68
68
|
export async function loadPublishedForDiff(opts) {
|
|
69
69
|
const { siteOrigin, logger } = opts;
|
|
70
70
|
let remotePages = null;
|
|
71
|
-
|
|
71
|
+
/** `undefined` = the site did not say. See `PublishedContent`. */
|
|
72
|
+
let remoteSiteConfig;
|
|
72
73
|
if (siteOrigin) {
|
|
73
74
|
try {
|
|
74
75
|
const res = await fetch(`${siteOrigin.replace(/\/+$/, "")}/api/editor/pages`, {
|
|
@@ -78,7 +79,13 @@ export async function loadPublishedForDiff(opts) {
|
|
|
78
79
|
const data = (await res.json());
|
|
79
80
|
if (Array.isArray(data.pages)) {
|
|
80
81
|
remotePages = data.pages;
|
|
81
|
-
|
|
82
|
+
/*
|
|
83
|
+
* Absent and `null` are different answers and must stay different.
|
|
84
|
+
* `null` is the site saying it publishes no header config; absent is
|
|
85
|
+
* an editor API that does not report one at all, which is most of
|
|
86
|
+
* them — an Astro site returns `{pages}` and nothing else.
|
|
87
|
+
*/
|
|
88
|
+
remoteSiteConfig = "siteConfig" in data ? (data.siteConfig ?? null) : undefined;
|
|
82
89
|
}
|
|
83
90
|
}
|
|
84
91
|
else {
|
|
@@ -89,17 +96,29 @@ export async function loadPublishedForDiff(opts) {
|
|
|
89
96
|
logger.warn({ siteOrigin, err: String(err) }, "publish/diff: site fetch failed, falling back");
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
|
-
// Hot path: remote
|
|
93
|
-
if (remotePages && remoteSiteConfig) {
|
|
99
|
+
// Hot path: the remote answered about both.
|
|
100
|
+
if (remotePages && remoteSiteConfig !== undefined) {
|
|
94
101
|
return { pages: remotePages, siteConfig: remoteSiteConfig, source: "site" };
|
|
95
102
|
}
|
|
96
|
-
// Either the remote didn't run, didn't
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
103
|
+
// Either the remote didn't run, didn't mention siteConfig, or didn't include
|
|
104
|
+
// pages. Read the JSON file to backfill the missing pieces — without it, a
|
|
105
|
+
// missing siteConfig would be misread as "header added · all fields" on
|
|
106
|
+
// every publish-diff load.
|
|
100
107
|
const fromFile = await readPublishedJsonFile();
|
|
101
108
|
const pages = remotePages ?? fromFile.pages;
|
|
102
|
-
|
|
109
|
+
/*
|
|
110
|
+
* The file is `apps/site`'s published content, so it may only answer for
|
|
111
|
+
* `apps/site`. When the remote supplied the pages it is the authority on its
|
|
112
|
+
* own header config, and a site that did not mention one has not said it has
|
|
113
|
+
* none — it has said nothing, which `publishDiffAction` reads as "no header
|
|
114
|
+
* change" rather than as a change.
|
|
115
|
+
*
|
|
116
|
+
* Backfilling here instead is what made every diff for a site whose editor
|
|
117
|
+
* API returns only `{pages}` report the bundled demo's name, logo, navGroups
|
|
118
|
+
* and navLabels as the "before" — seven changed fields, all of them
|
|
119
|
+
* describing a site nobody had asked about.
|
|
120
|
+
*/
|
|
121
|
+
const siteConfig = remotePages ? undefined : (remoteSiteConfig ?? fromFile.siteConfig);
|
|
103
122
|
if (pages)
|
|
104
123
|
return { pages, siteConfig, source: remotePages ? "site" : "file" };
|
|
105
124
|
logger.warn("publish/diff: falling back to in-memory publishedPages — diff may be inaccurate");
|
package/dist/ops/ops-engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { blockSchemas, operationSchema, validateBlockProps, findManifestSchemaIssue, isChrome, getMediaFields, isInBlockCatalogue, catalogueBlockTypes, mapSemanticThemeTokens, generateItemId, isRichTextDoc, fromMarkdown, mergeRichTextDoc } from "@avocadostudio-ai/shared";
|
|
2
|
+
import { blockSchemas, operationSchema, validateBlockProps, findManifestSchemaIssue, isChrome, getMediaFields, isInBlockCatalogue, catalogueBlockTypes, mapSemanticThemeTokens, generateItemId, isRichTextDoc, fromMarkdown, mergeRichTextDoc, resolveManifestFieldMeta } from "@avocadostudio-ai/shared";
|
|
3
3
|
import { normalizeRouteCandidate } from "../nlp/intent-helpers.js";
|
|
4
4
|
import { pageIdFromSlug, pageTitleFromSlug } from "../nlp/plan-normalizer.js";
|
|
5
5
|
import { OperationError, toErrorDetail as _unifiedToErrorDetail } from "../errors.js";
|
|
@@ -692,6 +692,49 @@ function _validateWithManifestIfPresent(manifestByType, blockType, nextProps) {
|
|
|
692
692
|
}
|
|
693
693
|
return propCheck.data;
|
|
694
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* A row added to a polymorphic list must say which shape it is.
|
|
697
|
+
*
|
|
698
|
+
* A list whose field meta carries a `discriminator` holds several shapes, and
|
|
699
|
+
* the row's value for that key is the only thing that says which. A row that
|
|
700
|
+
* omits it is not a row with one field missing: it is a row that corresponds to
|
|
701
|
+
* nothing the site can draw, and on a CMS that stores each row as its own
|
|
702
|
+
* document — `component` in Storyblok, `_type` in Sanity — it cannot be
|
|
703
|
+
* constructed at all. An adapter's only choices are to drop it or to guess.
|
|
704
|
+
*
|
|
705
|
+
* Dropping it is what happened. Asked for a statistics section, a planner added
|
|
706
|
+
* a card grid and filled it with four rows carrying every declared field and no
|
|
707
|
+
* `component`; the adapter skipped all four; **the page rendered the grid's
|
|
708
|
+
* heading over empty white space**, and the only trace anywhere was a
|
|
709
|
+
* `console.warn` in a dev server log. The manifest had the answer the whole
|
|
710
|
+
* time — the list declares its discriminator and names the types it admits.
|
|
711
|
+
*
|
|
712
|
+
* So reject it here, in the same place and the same category as an unknown
|
|
713
|
+
* prop, where the failure is loud and lands at the moment of the mistake with
|
|
714
|
+
* the admissible types in the message.
|
|
715
|
+
*
|
|
716
|
+
* Only omission is rejected. A value naming no branch is left alone on purpose:
|
|
717
|
+
* `itemFieldsByType` is derived from a schema that may enumerate fewer types
|
|
718
|
+
* than the CMS has, and `resolveListItemFields` already falls back to the union
|
|
719
|
+
* for exactly that case. Rejecting there would refuse rows a site can render.
|
|
720
|
+
*/
|
|
721
|
+
function _requireListDiscriminator(manifestByType, blockType, listKey, item) {
|
|
722
|
+
const definition = manifestByType.get(blockType);
|
|
723
|
+
if (!definition)
|
|
724
|
+
return;
|
|
725
|
+
const listField = resolveManifestFieldMeta(definition).listFields[listKey];
|
|
726
|
+
const discriminator = listField?.discriminator;
|
|
727
|
+
if (!discriminator)
|
|
728
|
+
return;
|
|
729
|
+
const value = item[discriminator];
|
|
730
|
+
if (typeof value === "string" && value.length > 0)
|
|
731
|
+
return;
|
|
732
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
733
|
+
return;
|
|
734
|
+
const admissible = Object.keys(listField.itemFieldsByType ?? {}).filter((t) => t.length > 0);
|
|
735
|
+
const choices = admissible.length > 0 ? `: ${admissible.join(", ")}` : "";
|
|
736
|
+
throw new OperationError(`A new ${listKey} row must set "${discriminator}", which names its type${choices}`, { category: "schema_violation" });
|
|
737
|
+
}
|
|
695
738
|
function _requireManifestComponent(manifestByType, blockType, operationName) {
|
|
696
739
|
if (manifestByType.size === 0)
|
|
697
740
|
return;
|
|
@@ -1207,6 +1250,7 @@ async function _applyOpsAtomicallyUnsafe(session, ops, options) {
|
|
|
1207
1250
|
// (id included), and a duplicate id would make every later id lookup
|
|
1208
1251
|
// silently resolve to the first occurrence.
|
|
1209
1252
|
const newItem = structuredClone(op.item);
|
|
1253
|
+
_requireListDiscriminator(manifestByType, block.type, op.listKey, newItem);
|
|
1210
1254
|
if (typeof newItem.id !== "string" ||
|
|
1211
1255
|
newItem.id.length === 0 ||
|
|
1212
1256
|
_itemIndexById(nextList, newItem.id) !== -1) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/orchestrator-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./package.json": "./package.json",
|
|
@@ -22,20 +22,8 @@
|
|
|
22
22
|
"openai": "^4.87.1",
|
|
23
23
|
"sharp": "^0.34.5",
|
|
24
24
|
"zod": "^4.3.6",
|
|
25
|
-
"@avocadostudio-ai/
|
|
26
|
-
"@avocadostudio-ai/
|
|
27
|
-
},
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"googleapis": "^171.4.0",
|
|
30
|
-
"@google/genai": "^1.46.0"
|
|
31
|
-
},
|
|
32
|
-
"peerDependenciesMeta": {
|
|
33
|
-
"googleapis": {
|
|
34
|
-
"optional": true
|
|
35
|
-
},
|
|
36
|
-
"@google/genai": {
|
|
37
|
-
"optional": true
|
|
38
|
-
}
|
|
25
|
+
"@avocadostudio-ai/shared": "^0.8.0",
|
|
26
|
+
"@avocadostudio-ai/migration-sdk": "^0.8.0"
|
|
39
27
|
},
|
|
40
28
|
"devDependencies": {
|
|
41
29
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
|
@@ -70,12 +58,23 @@
|
|
|
70
58
|
"license": "Apache-2.0",
|
|
71
59
|
"homepage": "https://docs.avocadostudio.dev",
|
|
72
60
|
"bugs": {
|
|
73
|
-
"url": "https://
|
|
61
|
+
"url": "https://docs.avocadostudio.dev"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"googleapis": "^171.4.0",
|
|
65
|
+
"@google/genai": "^1.46.0",
|
|
66
|
+
"@anthropic-ai/claude-agent-sdk": ">=0.3.0"
|
|
74
67
|
},
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
"peerDependenciesMeta": {
|
|
69
|
+
"googleapis": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"@google/genai": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@anthropic-ai/claude-agent-sdk": {
|
|
76
|
+
"optional": true
|
|
77
|
+
}
|
|
79
78
|
},
|
|
80
79
|
"scripts": {
|
|
81
80
|
"typecheck": "tsc --noEmit",
|