@avocadostudio-ai/orchestrator-core 0.25.0 → 0.27.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.
|
@@ -64,7 +64,8 @@ import { createCmsBootstrapCache } from "../cms/bootstrap.js";
|
|
|
64
64
|
import { resolveCapabilities } from "../cms/adapter.js";
|
|
65
65
|
import { mediaSourceFromUnknown } from "../cms/media-sources.js";
|
|
66
66
|
import { extractAccessToken, isAccessGateEnabled, isValidAccessToken, mintAccessToken, verifyAccessPassword } from "../http/access-tokens.js";
|
|
67
|
-
import {
|
|
67
|
+
import { editorCredentials } from "../http/editor-credentials.js";
|
|
68
|
+
import { clearLibraryDispatch, declareLibraryMount, observeLibraryMount, registerLibraryDispatch } from "./library-mount.js";
|
|
68
69
|
import { checkAuth, resolveAuth, CLOSED_HINT } from "./auth.js";
|
|
69
70
|
import { setSiteAssetLister, invalidateSiteAssets } from "../state/site-assets.js";
|
|
70
71
|
async function buildRuntime(config) {
|
|
@@ -262,6 +263,7 @@ const SUPPORTED_ROUTES = [
|
|
|
262
263
|
"GET /health",
|
|
263
264
|
"GET /auth/status",
|
|
264
265
|
"POST /auth/verify",
|
|
266
|
+
"GET /editor/credentials",
|
|
265
267
|
"POST /chat",
|
|
266
268
|
"POST /chat/stream",
|
|
267
269
|
"POST /chat/start",
|
|
@@ -536,9 +538,23 @@ export function createOrchestrator(config = {}) {
|
|
|
536
538
|
// See `library-mount.ts` for why this is not simply a config value.
|
|
537
539
|
if (config.previewUrl)
|
|
538
540
|
declareLibraryMount(`${config.previewUrl.replace(/\/+$/, "")}${basePath}`);
|
|
541
|
+
/*
|
|
542
|
+
* Requests this process's own preview dispatches in-process (see
|
|
543
|
+
* `library-mount.ts`). Membership is the credential: a request that arrived
|
|
544
|
+
* over the network is a different object, so it can never be in the set, and
|
|
545
|
+
* nothing about it — no header, no URL — can put it there. The bypass covers
|
|
546
|
+
* the three GET draft reads the SDK makes and nothing else, so code that
|
|
547
|
+
* reaches the dispatcher can read the drafts the preview renders, but never
|
|
548
|
+
* edit or publish.
|
|
549
|
+
*/
|
|
550
|
+
const inProcessRequests = new WeakSet();
|
|
551
|
+
const IN_PROCESS_READS = new Set(["/draft/pages", "/draft/slugs", "/draft/site-config"]);
|
|
539
552
|
const handler = async function handler(request) {
|
|
540
553
|
const url = new URL(request.url);
|
|
541
|
-
|
|
554
|
+
const inProcess = inProcessRequests.has(request);
|
|
555
|
+
// An in-process URL is synthetic; recording it would mislead every HTTP reader.
|
|
556
|
+
if (!inProcess)
|
|
557
|
+
observeLibraryMount(`${url.origin}${basePath}`);
|
|
542
558
|
const path = stripBasePath(url.pathname, basePath);
|
|
543
559
|
const cors = corsHeadersFor(request, config);
|
|
544
560
|
if (request.method === "OPTIONS") {
|
|
@@ -564,9 +580,12 @@ export function createOrchestrator(config = {}) {
|
|
|
564
580
|
* (the /auth exchange itself, and image reads an <img> tag cannot
|
|
565
581
|
* authenticate) pass through inside checkAuth.
|
|
566
582
|
*/
|
|
567
|
-
const
|
|
568
|
-
if (
|
|
569
|
-
|
|
583
|
+
const trustedRead = inProcess && request.method === "GET" && IN_PROCESS_READS.has(path);
|
|
584
|
+
if (!trustedRead) {
|
|
585
|
+
const gate = await checkAuth({ request, path, auth: config.auth, cors, log: gateLogger });
|
|
586
|
+
if (gate.response)
|
|
587
|
+
return gate.response;
|
|
588
|
+
}
|
|
570
589
|
if (request.method === "GET" && path === "/auth/status") {
|
|
571
590
|
/*
|
|
572
591
|
* `gateEnabled` answers one question for the editor's PasswordGate: "do I
|
|
@@ -643,6 +662,16 @@ export function createOrchestrator(config = {}) {
|
|
|
643
662
|
}
|
|
644
663
|
return jsonResponse({ ok: true, accessToken: mintAccessToken() }, { cors });
|
|
645
664
|
}
|
|
665
|
+
/*
|
|
666
|
+
* The site's draft secret and publish token, for a caller the gate above has
|
|
667
|
+
* already let in. See `http/editor-credentials.ts` for why these no longer
|
|
668
|
+
* travel in the editor's HTML. Not in `PUBLIC_PATHS`, and that is the point:
|
|
669
|
+
* on a mount with a credential, a request without a valid token never gets
|
|
670
|
+
* here.
|
|
671
|
+
*/
|
|
672
|
+
if (request.method === "GET" && path === "/editor/credentials") {
|
|
673
|
+
return jsonResponse(editorCredentials(), { cors: { ...cors, "cache-control": "no-store" } });
|
|
674
|
+
}
|
|
646
675
|
if (request.method === "POST" && path === "/chat") {
|
|
647
676
|
const runtime = await getRuntime();
|
|
648
677
|
await runtime.ready;
|
|
@@ -2635,7 +2664,13 @@ export function createOrchestrator(config = {}) {
|
|
|
2635
2664
|
"Use the Fastify orchestrator at apps/orchestrator for the full surface (sites, the session directory, the agent surface, etc.)."
|
|
2636
2665
|
}, { status: 405, cors });
|
|
2637
2666
|
};
|
|
2667
|
+
const dispatchInProcess = (request) => {
|
|
2668
|
+
inProcessRequests.add(request);
|
|
2669
|
+
return handler(request);
|
|
2670
|
+
};
|
|
2671
|
+
registerLibraryDispatch(basePath, dispatchInProcess);
|
|
2638
2672
|
handler.dispose = async () => {
|
|
2673
|
+
clearLibraryDispatch(dispatchInProcess);
|
|
2639
2674
|
if (!runtimePromise)
|
|
2640
2675
|
return;
|
|
2641
2676
|
try {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export declare const LIBRARY_MOUNT_KEY = "__avocado_library_mount__";
|
|
2
|
+
export type LibraryDispatch = (request: Request) => Promise<Response>;
|
|
2
3
|
/** Called at construction, from `previewUrl` + `basePath`. */
|
|
3
4
|
export declare function declareLibraryMount(url: string): void;
|
|
4
5
|
/** Called on each request, from the URL the runtime actually received. */
|
|
5
6
|
export declare function observeLibraryMount(url: string): void;
|
|
6
7
|
export declare function getLibraryMount(): string | null;
|
|
8
|
+
/** Called at construction: this handler answers in-process draft reads. */
|
|
9
|
+
export declare function registerLibraryDispatch(basePath: string, dispatch: LibraryDispatch): void;
|
|
10
|
+
/** Called on dispose, and only clears the slot if it is still this handler's. */
|
|
11
|
+
export declare function clearLibraryDispatch(dispatch: LibraryDispatch): void;
|
|
@@ -43,3 +43,17 @@ export function getLibraryMount() {
|
|
|
43
43
|
const s = slot();
|
|
44
44
|
return s.observed ?? s.declared ?? null;
|
|
45
45
|
}
|
|
46
|
+
/** Called at construction: this handler answers in-process draft reads. */
|
|
47
|
+
export function registerLibraryDispatch(basePath, dispatch) {
|
|
48
|
+
const s = slot();
|
|
49
|
+
s.basePath = basePath.replace(/\/+$/, "");
|
|
50
|
+
s.dispatch = dispatch;
|
|
51
|
+
}
|
|
52
|
+
/** Called on dispose, and only clears the slot if it is still this handler's. */
|
|
53
|
+
export function clearLibraryDispatch(dispatch) {
|
|
54
|
+
const s = slot();
|
|
55
|
+
if (s.dispatch !== dispatch)
|
|
56
|
+
return;
|
|
57
|
+
delete s.dispatch;
|
|
58
|
+
delete s.basePath;
|
|
59
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two site credentials a signed-in editor needs, served by
|
|
3
|
+
* `GET /editor/credentials` instead of being written into the editor's HTML.
|
|
4
|
+
*
|
|
5
|
+
* - `siteDraftSecret` rides on every preview URL the editor builds, because the
|
|
6
|
+
* draft cookie is routinely blocked in a cross-origin iframe. Without it a
|
|
7
|
+
* production site renders its published pages and nothing is selectable.
|
|
8
|
+
* - `publishToken` is the `x-publish-token` the standalone server checks on
|
|
9
|
+
* publish (library mode authenticates publish with the access token instead).
|
|
10
|
+
*
|
|
11
|
+
* Both used to reach the browser only through `window.__AVOCADO_CONFIG__`,
|
|
12
|
+
* which the self-host CLI writes into `index.html`. That page is served before
|
|
13
|
+
* the password gate runs, so an editor hosted on a public URL handed the site's
|
|
14
|
+
* draft secret to anyone who loaded it. With that secret, anyone could read the
|
|
15
|
+
* CMS's unpublished drafts through the site's preview route, password or not.
|
|
16
|
+
* Served from here, they reach only a caller the access gate has already let in.
|
|
17
|
+
*
|
|
18
|
+
* Absent values are omitted rather than sent as empty strings, so the editor
|
|
19
|
+
* keeps whatever it was configured with when this process holds nothing.
|
|
20
|
+
*/
|
|
21
|
+
export interface EditorCredentials {
|
|
22
|
+
siteDraftSecret?: string;
|
|
23
|
+
publishToken?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function editorCredentials(env?: Record<string, string | undefined>): EditorCredentials;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getConfiguredDraftSecret } from "@avocadostudio-ai/shared";
|
|
2
|
+
export function editorCredentials(env = process.env) {
|
|
3
|
+
const siteDraftSecret = getConfiguredDraftSecret(env) ?? undefined;
|
|
4
|
+
const publishToken = env.PUBLISH_TOKEN?.trim() || undefined;
|
|
5
|
+
return {
|
|
6
|
+
...(siteDraftSecret ? { siteDraftSecret } : {}),
|
|
7
|
+
...(publishToken ? { publishToken } : {})
|
|
8
|
+
};
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/orchestrator-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
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.
|
|
26
|
-
"@avocadostudio-ai/shared": "^0.
|
|
25
|
+
"@avocadostudio-ai/migration-sdk": "^0.27.0",
|
|
26
|
+
"@avocadostudio-ai/shared": "^0.27.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|