@avocadostudio-ai/orchestrator-core 0.2.0 → 0.2.3

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/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @avocadostudio-ai/orchestrator-core
2
+
3
+ The Avocado Studio orchestrator as a library: session state, AI planning, the
4
+ operations engine, and publishing, behind one Web-standard
5
+ `(Request) => Promise<Response>`.
6
+
7
+ This is the package **library mode is built on**. If you are integrating a
8
+ Next.js site you almost certainly want
9
+ [`@avocadostudio-ai/site-sdk`](https://www.npmjs.com/package/@avocadostudio-ai/site-sdk)
10
+ instead — it re-exports everything below from
11
+ `@avocadostudio-ai/site-sdk/server` and adds the Next-specific pieces (draft
12
+ mode, the page factory, the editor API route). Reach for this package directly
13
+ only when your host is not Next.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @avocadostudio-ai/orchestrator-core
19
+ ```
20
+
21
+ `better-sqlite3` is a real dependency, not optional: session state — draft
22
+ pages, undo/redo, the version log, chat history — lives in SQLite. Prebuilt
23
+ binaries ship for linux-x64 (glibc 2.28+), darwin-arm64 and darwin-x64 on
24
+ Node 22.
25
+
26
+ Two peers are **optional**: `googleapis` and `@google/genai`. They are reached
27
+ through `await import(...)` so a deployment that uses neither need not install
28
+ them — but a bundler resolves dynamic imports statically and will fail the build
29
+ over a package that is deliberately absent. Mark them external. On Next,
30
+ `withAvocado` from `@avocadostudio-ai/site-sdk/next-config` does it for you.
31
+
32
+ ## Mount it
33
+
34
+ ```ts
35
+ import { createOrchestrator } from "@avocadostudio-ai/orchestrator-core"
36
+
37
+ const handler = createOrchestrator({
38
+ basePath: "/api/avocado",
39
+ adapter: myAdapter,
40
+ auth: async (request) => Boolean(await getSession(request)),
41
+ })
42
+
43
+ // Any host that speaks Request/Response
44
+ export { handler as GET, handler as POST, handler as OPTIONS }
45
+ ```
46
+
47
+ `createOrchestrator` gates **every** route. With neither an `auth` hook nor a
48
+ credential (`ACCESS_PASSWORD_HASH` or `ORCHESTRATOR_ACCESS_TOKEN`) it refuses
49
+ all requests under `NODE_ENV=production` rather than serving your content
50
+ openly. Only `/auth/status` and `/auth/verify` are public.
51
+
52
+ ## The adapter
53
+
54
+ `adapter` is your content store. SQLite is the working copy; the adapter is the
55
+ source of truth, read on a cold session and written on publish.
56
+
57
+ ```ts
58
+ import type { CmsAdapter } from "@avocadostudio-ai/orchestrator-core/cms"
59
+
60
+ const myAdapter: CmsAdapter = {
61
+ id: "my-cms",
62
+ perspectives: true, // does getPages honour options.perspective?
63
+ getPages: (options) => fetchPages(options),
64
+ onPublish: (pages, config, context) => writeBack(pages, context?.published),
65
+ capabilities: { createPage: false }, // static — /whoami answers it offline
66
+ }
67
+ ```
68
+
69
+ `jsonFileAdapter` and `editorApiAdapter` are bundled from the same subpath. The
70
+ full contract — including why `perspectives` defaults to *no* while capabilities
71
+ default to *yes*, and why `onPublish` has to diff rather than overwrite — is
72
+ documented in
73
+ [the site-sdk README](https://www.npmjs.com/package/@avocadostudio-ai/site-sdk#the-adapter-contract).
74
+
75
+ ## What it does not do
76
+
77
+ It serves the API. It does not serve the editor UI — that is
78
+ `@avocadostudio-ai/cli` or your own deployment of the Studio — and it does not
79
+ render your pages.
80
+
81
+ `/health` is **not** a route, so a client that probes it to check compatibility
82
+ gets a 405. The body lists every route that does exist.
83
+
84
+ ## Persistence
85
+
86
+ | variable | meaning |
87
+ |---|---|
88
+ | `ORCHESTRATOR_DB_FILE` | Path to the SQLite file. Empty for the default; `:memory:` to force ephemeral. Auto-`:memory:` under `NODE_ENV=test` |
89
+ | `ORCHESTRATOR_DB_BACKUP_INTERVAL_HOURS` | Periodic `VACUUM INTO` snapshot interval (default 24) |
90
+ | `ORCHESTRATOR_DB_BACKUP_LIMIT` | Rolling snapshots to keep (default 14) |
91
+
92
+ Undo/redo is capped at 50 entries per slug per direction, the version log at
93
+ 100, recent edits at 10, chat history at 6 messages.
94
+
95
+ ## Providers
96
+
97
+ At least one of `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` or `GOOGLE_GENAI_API_KEY`
98
+ is required for planning. Keys never leave the process that holds them.
99
+
100
+ ## License
101
+
102
+ Apache-2.0
@@ -29,7 +29,13 @@ import { isAccessGateEnabled, isValidAccessToken, extractAccessToken } from "../
29
29
  * gating it would blank every uploaded image in the live site, not just in the
30
30
  * editor. Both are read-only and neither reveals session content.
31
31
  */
32
- const PUBLIC_PATHS = new Set(["/auth/status", "/auth/verify"]);
32
+ /*
33
+ * `/health` is here because a probe that needs a credential is not a probe. The
34
+ * CLI calls it before it has one, to compare protocol versions, and it answers
35
+ * nothing about the site's content — only that this is an Avocado orchestrator
36
+ * and which protocol it speaks.
37
+ */
38
+ const PUBLIC_PATHS = new Set(["/auth/status", "/auth/verify", "/health"]);
33
39
  export function isPublicPath(path, method) {
34
40
  if (method === "OPTIONS")
35
41
  return true;
@@ -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 } from "@avocadostudio-ai/shared";
24
+ import { operationSchema, blockManifestSchema, siteConfigSchema, declareBlockCatalogue, undeclaredBlockTypes, EDITOR_PROTOCOL_VERSION } from "@avocadostudio-ai/shared";
25
25
  import { chatRequestBodySchema } from "../nlp/intent-detection.js";
26
26
  import { applyOpsAtomically, pickFocusBlockId, pickUpdatedSlug, toErrorDetail, classifyGuardrailError } from "../ops/ops-engine.js";
27
27
  import { runChatStream, formatSseFrame } from "../http/chat-stream.js";
@@ -196,6 +196,7 @@ function jsonResponse(body, init = {}) {
196
196
  * only thing that keeps a list like this honest.
197
197
  */
198
198
  const SUPPORTED_ROUTES = [
199
+ "GET /health",
199
200
  "GET /auth/status",
200
201
  "POST /auth/verify",
201
202
  "POST /chat",
@@ -899,7 +900,9 @@ export function createOrchestrator(config = {}) {
899
900
  })),
900
901
  // Which content this is — see orchestrator-core/http/draft-provenance.ts.
901
902
  ...describeDraft({
902
- requestedSiteId: siteId ?? effectiveSiteId,
903
+ // Config first, matching every other route: a mount that names its
904
+ // site knows better than a request that forgot to.
905
+ requestedSiteId: effectiveSiteId ?? siteId,
903
906
  scopedSession,
904
907
  pageCount: pages.length,
905
908
  hasAdapter: Boolean(runtime.adapter)
@@ -1243,12 +1246,38 @@ export function createOrchestrator(config = {}) {
1243
1246
  }
1244
1247
  return actionResponse(blocksManifestAction(), cors);
1245
1248
  }
1249
+ /*
1250
+ * Liveness and protocol version, for a client deciding whether it can talk
1251
+ * to this build at all.
1252
+ *
1253
+ * The standalone orchestrator has always answered this; library mode never
1254
+ * did, so `avocado-cli start` probed it, got a 405, and printed
1255
+ * "Orchestrator /health returned 405. CLI started anyway." on every start —
1256
+ * a warning that means nothing, from the CLI's only compatibility check,
1257
+ * which could therefore never pass against an embedded mount.
1258
+ *
1259
+ * Deliberately before the auth gate (see `PUBLIC_PATHS`) and deliberately
1260
+ * content-free: it says what this process is, never what it holds.
1261
+ */
1262
+ if (request.method === "GET" && path === "/health") {
1263
+ return jsonResponse({ ok: true, mode: "library", protocolVersion: EDITOR_PROTOCOL_VERSION }, { cors });
1264
+ }
1246
1265
  if (request.method === "GET" && path === "/whoami") {
1247
1266
  const runtime = await getRuntime();
1248
1267
  await runtime.ready;
1249
1268
  const query = Object.fromEntries(url.searchParams);
1250
1269
  await runtime.bootstrapCache.ensure(scope(query.session, query.siteId), runtime.adapter, runtime.log);
1251
- return actionResponse(whoamiAction(query, url.origin, { hasAdapter: Boolean(runtime.adapter) }), cors);
1270
+ /*
1271
+ * `scope()` resolves the site the mount serves; `whoamiAction` re-derives
1272
+ * the session key from what it is handed. Handing it the raw query meant
1273
+ * the two disagreed whenever the caller omitted `siteId` — the bootstrap
1274
+ * seeded `library::default` and the answer was read from the legacy key,
1275
+ * so a correctly configured mount reported Avocado's bundled demo
1276
+ * content, `source: "demo"`, and all-true capabilities in place of the
1277
+ * adapter's own. The note even said "No siteId was supplied", which was
1278
+ * true of the request and false of the configuration.
1279
+ */
1280
+ return actionResponse(whoamiAction({ ...query, siteId: effectiveSiteId ?? query.siteId }, url.origin, { hasAdapter: Boolean(runtime.adapter) }), cors);
1252
1281
  }
1253
1282
  /*
1254
1283
  * Publishing status, history and preview.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avocadostudio-ai/orchestrator-core",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -23,8 +23,8 @@
23
23
  "openai": "^4.87.1",
24
24
  "sharp": "^0.34.5",
25
25
  "zod": "^4.3.6",
26
- "@avocadostudio-ai/migration-sdk": "0.2.0",
27
- "@avocadostudio-ai/shared": "0.2.0"
26
+ "@avocadostudio-ai/migration-sdk": "0.2.3",
27
+ "@avocadostudio-ai/shared": "0.2.3"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@google/genai": "^1.46.0",
@@ -44,6 +44,17 @@
44
44
  "dist"
45
45
  ],
46
46
  "description": "Core Avocado Studio orchestrator — session state, AI planning, operations engine, publishing",
47
+ "keywords": [
48
+ "avocado",
49
+ "avocado-studio",
50
+ "cms",
51
+ "ai",
52
+ "llm",
53
+ "content-editor",
54
+ "page-builder",
55
+ "headless-cms",
56
+ "visual-editing"
57
+ ],
47
58
  "license": "Apache-2.0",
48
59
  "homepage": "https://github.com/avocadostudio-ai/avocado/tree/main/packages/orchestrator-core#readme",
49
60
  "bugs": {