@avocadostudio-ai/orchestrator-core 0.11.5 → 0.11.6
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.
|
@@ -21,7 +21,7 @@ import { mkdir, writeFile, readFile } from "node:fs/promises";
|
|
|
21
21
|
import { resolve, basename } from "node:path";
|
|
22
22
|
import { randomUUID } from "node:crypto";
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import { operationSchema, blockManifestSchema, siteConfigSchema, declareBlockCatalogue, undeclaredBlockTypes, EDITOR_PROTOCOL_VERSION, withLoopbackSpellings } from "@avocadostudio-ai/shared";
|
|
24
|
+
import { operationSchema, blockManifestSchema, siteConfigSchema, declareBlockCatalogue, undeclaredBlockTypes, EDITOR_PROTOCOL_VERSION, withLoopbackSpellings, editorAllowHeaders } from "@avocadostudio-ai/shared";
|
|
25
25
|
import { keyFileHint } from "../env-file-location.js";
|
|
26
26
|
import { chatRequestBodySchema } from "../nlp/intent-detection.js";
|
|
27
27
|
import { applyOpsAtomically, pickFocusBlockId, pickUpdatedSlug, toErrorDetail, classifyGuardrailError } from "../ops/ops-engine.js";
|
|
@@ -536,7 +536,7 @@ export function createOrchestrator(config = {}) {
|
|
|
536
536
|
* would have been unreachable from the very client it is for.
|
|
537
537
|
*/
|
|
538
538
|
"access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
539
|
-
"access-control-allow-headers":
|
|
539
|
+
"access-control-allow-headers": editorAllowHeaders(),
|
|
540
540
|
"access-control-max-age": "600"
|
|
541
541
|
}
|
|
542
542
|
});
|
|
@@ -1158,6 +1158,23 @@ export function createOrchestrator(config = {}) {
|
|
|
1158
1158
|
* editor offers the picker's CMS tab on it, so silence hides the
|
|
1159
1159
|
* tab rather than opening one onto a method nobody implemented. */
|
|
1160
1160
|
cmsMedia: runtime.capabilities.readsMedia,
|
|
1161
|
+
/*
|
|
1162
|
+
* Whether `/audio/transcribe` can actually answer.
|
|
1163
|
+
*
|
|
1164
|
+
* This key was simply absent from library mode's feature list while
|
|
1165
|
+
* the standalone server has always reported it, and the editor's
|
|
1166
|
+
* check is `audioTranscription !== false` — so `undefined` read as
|
|
1167
|
+
* *available* and the microphone shipped visible in every
|
|
1168
|
+
* scaffolded project, including with no key configured at all.
|
|
1169
|
+
* Pressing it records audio and posts it to a route that can only
|
|
1170
|
+
* answer 503.
|
|
1171
|
+
*
|
|
1172
|
+
* The same providers as the standalone server, for the same reason:
|
|
1173
|
+
* transcription tries OpenAI first and falls back to Gemini, so
|
|
1174
|
+
* either key enables it and neither means the control must not be
|
|
1175
|
+
* offered.
|
|
1176
|
+
*/
|
|
1177
|
+
audioTranscription: Boolean(process.env.OPENAI_API_KEY?.trim() || process.env.GOOGLE_GENAI_API_KEY?.trim()),
|
|
1161
1178
|
agentMode: false
|
|
1162
1179
|
},
|
|
1163
1180
|
/*
|
|
@@ -75,20 +75,29 @@ export declare function blockContractsSummary(manifest?: BlockManifest): Record<
|
|
|
75
75
|
*/
|
|
76
76
|
export declare const KEYLESS_PLANNER_NOTICE: string;
|
|
77
77
|
/**
|
|
78
|
-
* The same notice,
|
|
78
|
+
* The same notice, without the *path*.
|
|
79
79
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
80
|
+
* An earlier version interpolated the absolute path of `.env.local` into the
|
|
81
|
+
* sentence. Correct, and it read appallingly: an 80-character path mid-
|
|
82
|
+
* paragraph, wrapping across four lines of a narrow pane, so a two-line
|
|
83
|
+
* explanation became a wall in which the actual advice was the least visible
|
|
84
|
+
* part — and it duplicated, word for word, the notice sitting directly below
|
|
85
|
+
* the conversation.
|
|
85
86
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
87
|
+
* The path belongs in exactly one place: the notice, where it is a selectable
|
|
88
|
+
* code block rather than prose. The variable names stay here, though, and a
|
|
89
|
+
* first attempt at removing those was wrong — `library-demo-identity.test.ts`
|
|
90
|
+
* pins them, and it is right to. **The notice is dismissible.** A message that
|
|
91
|
+
* says "see the notice below" is a dangling reference the moment someone closes
|
|
92
|
+
* it, and chat messages are also read in transcripts and telemetry where no
|
|
93
|
+
* notice exists. So this sentence has to stand alone; it just does not have to
|
|
94
|
+
* carry a filesystem path to do it.
|
|
95
|
+
*
|
|
96
|
+
* `envFilePath` is still accepted so callers need not know any of this. It
|
|
97
|
+
* changes nothing today and keeps the seam if a surface without the notice ever
|
|
98
|
+
* needs the long form.
|
|
90
99
|
*/
|
|
91
|
-
export declare function keylessPlannerNotice(
|
|
100
|
+
export declare function keylessPlannerNotice(_envFilePath?: string): string;
|
|
92
101
|
export declare function withKeylessNotice(plan: EditPlan, envFilePath?: string): EditPlan;
|
|
93
102
|
/**
|
|
94
103
|
* Drop the suggestions this planner could not act on.
|
|
@@ -625,27 +625,33 @@ export function blockContractsSummary(manifest) {
|
|
|
625
625
|
* So when the rules produce no operations, say why. The suggestions are kept:
|
|
626
626
|
* they are the phrasings this planner *can* execute without a key.
|
|
627
627
|
*/
|
|
628
|
-
export const KEYLESS_PLANNER_NOTICE = "
|
|
629
|
-
"
|
|
628
|
+
export const KEYLESS_PLANNER_NOTICE = "No API key is configured, so this is the built-in demo planner, which handles only simple, literal edits. " +
|
|
629
|
+
"Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your project's .env.local and restart the dev server for full chat.";
|
|
630
630
|
/**
|
|
631
|
-
* The same notice,
|
|
631
|
+
* The same notice, without the *path*.
|
|
632
632
|
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
633
|
+
* An earlier version interpolated the absolute path of `.env.local` into the
|
|
634
|
+
* sentence. Correct, and it read appallingly: an 80-character path mid-
|
|
635
|
+
* paragraph, wrapping across four lines of a narrow pane, so a two-line
|
|
636
|
+
* explanation became a wall in which the actual advice was the least visible
|
|
637
|
+
* part — and it duplicated, word for word, the notice sitting directly below
|
|
638
|
+
* the conversation.
|
|
638
639
|
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
640
|
+
* The path belongs in exactly one place: the notice, where it is a selectable
|
|
641
|
+
* code block rather than prose. The variable names stay here, though, and a
|
|
642
|
+
* first attempt at removing those was wrong — `library-demo-identity.test.ts`
|
|
643
|
+
* pins them, and it is right to. **The notice is dismissible.** A message that
|
|
644
|
+
* says "see the notice below" is a dangling reference the moment someone closes
|
|
645
|
+
* it, and chat messages are also read in transcripts and telemetry where no
|
|
646
|
+
* notice exists. So this sentence has to stand alone; it just does not have to
|
|
647
|
+
* carry a filesystem path to do it.
|
|
648
|
+
*
|
|
649
|
+
* `envFilePath` is still accepted so callers need not know any of this. It
|
|
650
|
+
* changes nothing today and keeps the seam if a surface without the notice ever
|
|
651
|
+
* needs the long form.
|
|
643
652
|
*/
|
|
644
|
-
export function keylessPlannerNotice(
|
|
645
|
-
|
|
646
|
-
return KEYLESS_PLANNER_NOTICE;
|
|
647
|
-
return ("There's no AI key configured, so I'm running on the built-in demo planner — it only handles simple, literal edits. " +
|
|
648
|
-
`Add ANTHROPIC_API_KEY (or OPENAI_API_KEY) to ${envFilePath} and restart the dev server to chat for real.`);
|
|
653
|
+
export function keylessPlannerNotice(_envFilePath) {
|
|
654
|
+
return KEYLESS_PLANNER_NOTICE;
|
|
649
655
|
}
|
|
650
656
|
export function withKeylessNotice(plan, envFilePath) {
|
|
651
657
|
if (plan.ops.length > 0)
|
|
@@ -159,15 +159,53 @@ function diffPage(before, after) {
|
|
|
159
159
|
const a = after;
|
|
160
160
|
const blockDiffs = diffBlocks(b.blocks, a.blocks);
|
|
161
161
|
const titleChanged = b.title !== a.title;
|
|
162
|
+
const metaDiffs = diffPageMeta(b, a);
|
|
162
163
|
const hasStructuralChange = blockDiffs.some((bd) => bd.status !== "unchanged");
|
|
163
164
|
return {
|
|
164
165
|
slug: a.slug,
|
|
165
|
-
status: titleChanged || hasStructuralChange ? "modified" : "unchanged",
|
|
166
|
+
status: titleChanged || metaDiffs.length > 0 || hasStructuralChange ? "modified" : "unchanged",
|
|
166
167
|
titleBefore: titleChanged ? b.title : undefined,
|
|
167
168
|
titleAfter: titleChanged ? a.title : undefined,
|
|
169
|
+
...(metaDiffs.length > 0 ? { metaDiffs } : {}),
|
|
168
170
|
blockDiffs,
|
|
169
171
|
};
|
|
170
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Page-level SEO fields, which the diff did not look at.
|
|
175
|
+
*
|
|
176
|
+
* A page's `meta` carries the title, description, Open Graph image and public
|
|
177
|
+
* path — everything a crawler and a social card read, and the whole content of
|
|
178
|
+
* the property panel's **SEO** section. None of it reached the publish review,
|
|
179
|
+
* so a user who edited their page title saw "1 page modified · 1 field changed"
|
|
180
|
+
* counting only the block they had also touched, and no mention of the title at
|
|
181
|
+
* all. The draft held the change; the screen that exists to show what will
|
|
182
|
+
* change did not show it.
|
|
183
|
+
*
|
|
184
|
+
* `ogImage` is typed as an image so the UI renders a thumbnail rather than a
|
|
185
|
+
* URL, matching how block image fields already behave.
|
|
186
|
+
*/
|
|
187
|
+
const PAGE_META_FIELDS = [
|
|
188
|
+
["title", "text"],
|
|
189
|
+
["description", "text"],
|
|
190
|
+
["ogImage", "image"],
|
|
191
|
+
["path", "other"]
|
|
192
|
+
];
|
|
193
|
+
function diffPageMeta(before, after) {
|
|
194
|
+
const b = (before.meta ?? {});
|
|
195
|
+
const a = (after.meta ?? {});
|
|
196
|
+
const out = [];
|
|
197
|
+
for (const [key, kind] of PAGE_META_FIELDS) {
|
|
198
|
+
const bv = b[key];
|
|
199
|
+
const av = a[key];
|
|
200
|
+
// Absent and empty are the same thing to a reader, and treating them as
|
|
201
|
+
// different produced phantom diffs on pages that had never had a value.
|
|
202
|
+
const normalise = (v) => (v === undefined || v === null || v === "" ? undefined : v);
|
|
203
|
+
if (normalise(bv) === normalise(av))
|
|
204
|
+
continue;
|
|
205
|
+
out.push({ path: `meta.${key}`, before: bv, after: av, kind });
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
171
209
|
// Keys of SiteConfig that drive the rendered SiteHeader chrome. We diff
|
|
172
210
|
// only these to keep the publish UI focused on user-visible header changes;
|
|
173
211
|
// `purpose`, `tone`, `constraints`, `themeOverrides` are AI/style context
|
|
@@ -276,6 +314,15 @@ export function computePublishDiff(draft, published, options) {
|
|
|
276
314
|
if (bd.fieldDiffs)
|
|
277
315
|
totalChangedFields += bd.fieldDiffs.length;
|
|
278
316
|
}
|
|
317
|
+
/*
|
|
318
|
+
* Page metadata counts as changed fields too.
|
|
319
|
+
*
|
|
320
|
+
* Three counters used to disagree on one screen — "1 field changed" here
|
|
321
|
+
* (blocks only), "2 changes" on the page row (which counted an added
|
|
322
|
+
* block), and "Publish 1 change" on the button — and a metadata edit moved
|
|
323
|
+
* none of them.
|
|
324
|
+
*/
|
|
325
|
+
totalChangedFields += diff.metaDiffs?.length ?? 0;
|
|
279
326
|
}
|
|
280
327
|
const statusOrder = {
|
|
281
328
|
added: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/orchestrator-core",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./package.json": "./package.json",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"openai": "^4.87.1",
|
|
23
23
|
"sharp": "^0.35.4",
|
|
24
24
|
"zod": "^4.3.6",
|
|
25
|
-
"@avocadostudio-ai/migration-sdk": "^0.11.
|
|
26
|
-
"@avocadostudio-ai/shared": "^0.11.
|
|
25
|
+
"@avocadostudio-ai/migration-sdk": "^0.11.6",
|
|
26
|
+
"@avocadostudio-ai/shared": "^0.11.6"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|