@avocadostudio-ai/orchestrator-core 0.3.1 → 0.3.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/dist/chat/anthropic-planner.d.ts +8 -0
- package/dist/chat/anthropic-planner.js +166 -12
- package/dist/chat/chat-pipeline-translation.d.ts +13 -0
- package/dist/chat/chat-pipeline-translation.js +109 -45
- package/dist/chat/chat-pipeline.d.ts +1 -1
- package/dist/chat/chat-pipeline.js +297 -53
- package/dist/chat/gemini-planner.d.ts +2 -0
- package/dist/chat/gemini-planner.js +2 -1
- package/dist/chat/hallucination-validator.d.ts +6 -0
- package/dist/chat/hallucination-validator.js +49 -8
- package/dist/chat/planner-types.d.ts +15 -0
- package/dist/chat/planner-types.js +2 -2
- package/dist/chat/planner.d.ts +12 -0
- package/dist/chat/planner.js +16 -2
- package/dist/chat/translation-chunking.d.ts +124 -0
- package/dist/chat/translation-chunking.js +371 -0
- package/dist/checks/field-walk.d.ts +25 -0
- package/dist/checks/field-walk.js +152 -0
- package/dist/checks/index.d.ts +5 -0
- package/dist/checks/index.js +4 -0
- package/dist/checks/page-weight.d.ts +22 -0
- package/dist/checks/page-weight.js +200 -0
- package/dist/checks/rules-draft.d.ts +2 -0
- package/dist/checks/rules-draft.js +375 -0
- package/dist/checks/run-checks.d.ts +32 -0
- package/dist/checks/run-checks.js +152 -0
- package/dist/checks/session-runner.d.ts +19 -0
- package/dist/checks/session-runner.js +95 -0
- package/dist/checks/types.d.ts +65 -0
- package/dist/checks/types.js +1 -0
- package/dist/cms/adapter.d.ts +1 -0
- package/dist/durable/durable-store-singleton.d.ts +37 -0
- package/dist/durable/durable-store-singleton.js +179 -0
- package/dist/durable/finding-impact.d.ts +30 -0
- package/dist/durable/finding-impact.js +53 -0
- package/dist/durable/in-memory-durable-store.d.ts +203 -0
- package/dist/durable/in-memory-durable-store.js +363 -0
- package/dist/durable/index.d.ts +5 -0
- package/dist/durable/index.js +4 -0
- package/dist/durable/pending-plan-store.d.ts +28 -0
- package/dist/durable/pending-plan-store.js +156 -0
- package/dist/durable/sqlite-durable-store.d.ts +71 -0
- package/dist/durable/sqlite-durable-store.js +631 -0
- package/dist/durable/types.d.ts +265 -0
- package/dist/durable/types.js +1 -0
- package/dist/handler/create-orchestrator.d.ts +4 -0
- package/dist/handler/create-orchestrator.js +85 -9
- package/dist/http/audio-actions.d.ts +1 -1
- package/dist/http/checks-actions.d.ts +39 -0
- package/dist/http/checks-actions.js +122 -0
- package/dist/http/history-actions.d.ts +1 -1
- package/dist/http/image-generate-actions.d.ts +2 -2
- package/dist/http/ops-actions.d.ts +2 -2
- package/dist/http/publish-actions.d.ts +4 -4
- package/dist/http/restore-actions.d.ts +3 -3
- package/dist/http/screenshot-actions.d.ts +2 -2
- package/dist/http/session-actions.d.ts +1 -1
- package/dist/http/telemetry-feedback-actions.d.ts +2 -2
- package/dist/http/unsplash-actions.d.ts +2 -2
- package/dist/http/variations-actions.d.ts +2 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +27 -0
- package/dist/nlp/deterministic-planner-context.d.ts +16 -0
- package/dist/nlp/deterministic-planner-context.js +33 -7
- package/dist/nlp/deterministic-planner-suggestions.js +1 -1
- package/dist/nlp/plan-normalizer.js +193 -56
- package/dist/ops/destructive-action-gate.js +7 -2
- package/dist/ops/ops-engine.d.ts +12 -1
- package/dist/ops/ops-engine.js +41 -14
- package/dist/publish/publish-target-registry.js +1 -1
- package/dist/publish/publish-target.d.ts +1 -1
- package/dist/state/session-state.js +8 -1
- package/package.json +3 -3
package/dist/ops/ops-engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { blockSchemas, operationSchema, validateBlockProps, findManifestSchemaIssue, isChrome, 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 } 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";
|
|
@@ -88,7 +88,17 @@ function remapRouteReference(value, fromSlug, toSlug) {
|
|
|
88
88
|
}
|
|
89
89
|
return value;
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Rewrite every route reference under `input` from `fromSlug` to `toSlug`.
|
|
93
|
+
*
|
|
94
|
+
* The walk is permissive by design — it remaps any string that starts with the
|
|
95
|
+
* old slug, whatever prop holds it — because a custom block ships no field
|
|
96
|
+
* metadata and its links still have to survive a rename. `mediaKeys` is the one
|
|
97
|
+
* exception: props that address a *resource* rather than a page. Renaming
|
|
98
|
+
* `/pricing` to `/plans` does not move `/pricing/demo.mp4`, and before this the
|
|
99
|
+
* walker rewrote that path too, quietly breaking the asset.
|
|
100
|
+
*/
|
|
101
|
+
function rewriteRouteLinksInValue(input, fromSlug, toSlug, mediaKeys) {
|
|
92
102
|
if (typeof input === "string") {
|
|
93
103
|
const mapped = remapRouteReference(input, fromSlug, toSlug);
|
|
94
104
|
return { value: mapped, changed: mapped !== input };
|
|
@@ -96,7 +106,7 @@ function rewriteRouteLinksInValue(input, fromSlug, toSlug) {
|
|
|
96
106
|
if (Array.isArray(input)) {
|
|
97
107
|
let changed = false;
|
|
98
108
|
const next = input.map((item) => {
|
|
99
|
-
const mapped = rewriteRouteLinksInValue(item, fromSlug, toSlug);
|
|
109
|
+
const mapped = rewriteRouteLinksInValue(item, fromSlug, toSlug, mediaKeys);
|
|
100
110
|
if (mapped.changed)
|
|
101
111
|
changed = true;
|
|
102
112
|
return mapped.value;
|
|
@@ -109,11 +119,8 @@ function rewriteRouteLinksInValue(input, fromSlug, toSlug) {
|
|
|
109
119
|
let changed = false;
|
|
110
120
|
const next = {};
|
|
111
121
|
for (const [key, value] of Object.entries(source)) {
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
if (mapped !== value)
|
|
115
|
-
changed = true;
|
|
116
|
-
next[key] = mapped;
|
|
122
|
+
if (mediaKeys.has(key)) {
|
|
123
|
+
next[key] = value;
|
|
117
124
|
continue;
|
|
118
125
|
}
|
|
119
126
|
if (typeof value === "string" && key === "body") {
|
|
@@ -128,7 +135,7 @@ function rewriteRouteLinksInValue(input, fromSlug, toSlug) {
|
|
|
128
135
|
next[key] = rewritten;
|
|
129
136
|
continue;
|
|
130
137
|
}
|
|
131
|
-
const mapped = rewriteRouteLinksInValue(value, fromSlug, toSlug);
|
|
138
|
+
const mapped = rewriteRouteLinksInValue(value, fromSlug, toSlug, mediaKeys);
|
|
132
139
|
if (mapped.changed)
|
|
133
140
|
changed = true;
|
|
134
141
|
next[key] = mapped.value;
|
|
@@ -138,7 +145,7 @@ function rewriteRouteLinksInValue(input, fromSlug, toSlug) {
|
|
|
138
145
|
function rewriteLinksToRenamedPage(page, fromSlug, toSlug) {
|
|
139
146
|
let changed = false;
|
|
140
147
|
const nextBlocks = page.blocks.map((block) => {
|
|
141
|
-
const mapped = rewriteRouteLinksInValue(block.props, fromSlug, toSlug);
|
|
148
|
+
const mapped = rewriteRouteLinksInValue(block.props, fromSlug, toSlug, getMediaFields(block.type));
|
|
142
149
|
if (!mapped.changed)
|
|
143
150
|
return block;
|
|
144
151
|
changed = true;
|
|
@@ -208,16 +215,36 @@ export function classifyGuardrailError(reason) {
|
|
|
208
215
|
lower.includes("required") ||
|
|
209
216
|
lower.includes("unknown props") ||
|
|
210
217
|
lower.includes("out of range") ||
|
|
211
|
-
lower.includes("must be")
|
|
218
|
+
lower.includes("must be") ||
|
|
219
|
+
// Every "already exists" this engine raises — a duplicate block id, a page
|
|
220
|
+
// slug that is taken — it raises *as* a schema_violation. This list did not
|
|
221
|
+
// know the word, so a plan one renamed id away from applying classified as
|
|
222
|
+
// `internal_error`, which the repair pass refuses to touch. Callers holding
|
|
223
|
+
// the thrown error should prefer its `category` over this function; the
|
|
224
|
+
// keyword is here for the paths that only ever had the string.
|
|
225
|
+
lower.includes("already exists")) {
|
|
212
226
|
return "schema_violation";
|
|
213
227
|
}
|
|
214
228
|
return "internal_error";
|
|
215
229
|
}
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
/**
|
|
231
|
+
* `category` is the one the failure was actually thrown with, for callers that
|
|
232
|
+
* still hold it. Without it this falls back to reading the category back out of
|
|
233
|
+
* the message, which is a guess — see `isRepairEligibleCategory`.
|
|
234
|
+
*/
|
|
235
|
+
export function formatValidationError(reason, category) {
|
|
236
|
+
return `${category ?? classifyGuardrailError(reason)}: ${reason}`;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The deterministic repair pass exists for plans that are structurally wrong
|
|
240
|
+
* but fixable — a bad prop name, an id that clashes. Anything else is either
|
|
241
|
+
* hopeless or not the planner's fault, and re-prompting it wastes a model call.
|
|
242
|
+
*/
|
|
243
|
+
export function isRepairEligibleCategory(category) {
|
|
244
|
+
return category === "schema_violation";
|
|
218
245
|
}
|
|
219
246
|
export function isDeterministicRepairEligible(reason) {
|
|
220
|
-
return classifyGuardrailError(reason)
|
|
247
|
+
return isRepairEligibleCategory(classifyGuardrailError(reason));
|
|
221
248
|
}
|
|
222
249
|
/**
|
|
223
250
|
* Extracts structured fields from a planner schema_violation reason so the
|
|
@@ -10,7 +10,7 @@ import { DeployHookPublishTarget } from "./targets/deploy-hook.js";
|
|
|
10
10
|
* handling requests:
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
|
-
* import { registerPublishTarget } from "
|
|
13
|
+
* import { registerPublishTarget } from "@avocadostudio-ai/orchestrator-core"
|
|
14
14
|
* registerPublishTarget(new S3PublishTarget())
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
@@ -6,7 +6,7 @@ import type { PublishTracker } from "../state/session-state.ts";
|
|
|
6
6
|
* up writing content. Built-ins cover git + Vercel, a site-contract POST, and
|
|
7
7
|
* a raw Vercel deploy hook. Third parties (S3, GitLab Pages, custom CI, etc.)
|
|
8
8
|
* implement {@link PublishTarget} and register via
|
|
9
|
-
* {@link registerPublishTarget} from
|
|
9
|
+
* {@link registerPublishTarget} from `@avocadostudio-ai/orchestrator-core`.
|
|
10
10
|
*
|
|
11
11
|
* The route handler's job shrinks to: build {@link PublishContext}, call
|
|
12
12
|
* {@link selectPublishTarget}, invoke `target.publish(ctx)`, save the returned
|
|
@@ -3,6 +3,7 @@ import { dirname, resolve } from "node:path";
|
|
|
3
3
|
import { demoPublishedPages, demoSiteConfig, ensureItemIds, IMAGE_PLACEHOLDER } from "@avocadostudio-ai/shared";
|
|
4
4
|
import { toErrorDetail as _unifiedToErrorDetail } from "../errors.js";
|
|
5
5
|
import { archiveMigratedJson, getStore, notePersistenceFailure, notePersistenceSuccess, readLegacyJson, resolveDbFile, resolveJsonMigrationTtlDays, sweepStaleMigrations, } from "./sqlite-store-singleton.js";
|
|
6
|
+
import { discardPendingProposalsForSession, sweepExpiredProposals } from "../durable/durable-store-singleton.js";
|
|
6
7
|
// Re-exported so callers that already depend on this module for state can ask
|
|
7
8
|
// whether that state is actually being kept, without a second import path.
|
|
8
9
|
export { persistenceHealth, persistenceWarning } from "./sqlite-store-singleton.js";
|
|
@@ -173,6 +174,9 @@ export function forgetSession(sessionKey) {
|
|
|
173
174
|
pendingClarificationBySession.delete(sessionKey);
|
|
174
175
|
chatHistoryBySession.delete(sessionKey);
|
|
175
176
|
pendingApprovalPlanBySession.delete(sessionKey);
|
|
177
|
+
// Session ids are caller-supplied and routinely reused, so a pending proposal
|
|
178
|
+
// left behind here would rehydrate into the *next* session with this id.
|
|
179
|
+
discardPendingProposalsForSession(sessionKey);
|
|
176
180
|
imageSourcePreferenceBySession.delete(sessionKey);
|
|
177
181
|
publishStatusBySession.delete(sessionKey);
|
|
178
182
|
capabilitiesBySession.delete(sessionKey);
|
|
@@ -1091,12 +1095,15 @@ const EPHEMERAL_MAP_CAP = 500; // max entries for maps without timestamps
|
|
|
1091
1095
|
*/
|
|
1092
1096
|
export function evictStaleEphemeralMaps() {
|
|
1093
1097
|
const now = Date.now();
|
|
1094
|
-
// pendingApprovalPlanBySession: has createdAt — evict by age
|
|
1098
|
+
// pendingApprovalPlanBySession: has createdAt — evict by age. The durable
|
|
1099
|
+
// mirror carries the same TTL as `expiresAt`, so both halves age out together
|
|
1100
|
+
// rather than the Map forgetting a plan the store would hand straight back.
|
|
1095
1101
|
for (const [key, plan] of pendingApprovalPlanBySession) {
|
|
1096
1102
|
if (now - new Date(plan.createdAt).getTime() > APPROVAL_PLAN_TTL_MS) {
|
|
1097
1103
|
pendingApprovalPlanBySession.delete(key);
|
|
1098
1104
|
}
|
|
1099
1105
|
}
|
|
1106
|
+
sweepExpiredProposals();
|
|
1100
1107
|
// publishStatusBySession: has updatedAt — evict by age
|
|
1101
1108
|
for (const [key, tracker] of publishStatusBySession) {
|
|
1102
1109
|
if (now - new Date(tracker.updatedAt).getTime() > PUBLISH_STATUS_TTL_MS) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/orchestrator-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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/
|
|
27
|
-
"@avocadostudio-ai/
|
|
26
|
+
"@avocadostudio-ai/shared": "^0.3.3",
|
|
27
|
+
"@avocadostudio-ai/migration-sdk": "^0.3.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@google/genai": "^1.46.0",
|