@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.
Files changed (73) hide show
  1. package/dist/chat/anthropic-planner.d.ts +8 -0
  2. package/dist/chat/anthropic-planner.js +166 -12
  3. package/dist/chat/chat-pipeline-translation.d.ts +13 -0
  4. package/dist/chat/chat-pipeline-translation.js +109 -45
  5. package/dist/chat/chat-pipeline.d.ts +1 -1
  6. package/dist/chat/chat-pipeline.js +297 -53
  7. package/dist/chat/gemini-planner.d.ts +2 -0
  8. package/dist/chat/gemini-planner.js +2 -1
  9. package/dist/chat/hallucination-validator.d.ts +6 -0
  10. package/dist/chat/hallucination-validator.js +49 -8
  11. package/dist/chat/planner-types.d.ts +15 -0
  12. package/dist/chat/planner-types.js +2 -2
  13. package/dist/chat/planner.d.ts +12 -0
  14. package/dist/chat/planner.js +16 -2
  15. package/dist/chat/translation-chunking.d.ts +124 -0
  16. package/dist/chat/translation-chunking.js +371 -0
  17. package/dist/checks/field-walk.d.ts +25 -0
  18. package/dist/checks/field-walk.js +152 -0
  19. package/dist/checks/index.d.ts +5 -0
  20. package/dist/checks/index.js +4 -0
  21. package/dist/checks/page-weight.d.ts +22 -0
  22. package/dist/checks/page-weight.js +200 -0
  23. package/dist/checks/rules-draft.d.ts +2 -0
  24. package/dist/checks/rules-draft.js +375 -0
  25. package/dist/checks/run-checks.d.ts +32 -0
  26. package/dist/checks/run-checks.js +152 -0
  27. package/dist/checks/session-runner.d.ts +19 -0
  28. package/dist/checks/session-runner.js +95 -0
  29. package/dist/checks/types.d.ts +65 -0
  30. package/dist/checks/types.js +1 -0
  31. package/dist/cms/adapter.d.ts +1 -0
  32. package/dist/durable/durable-store-singleton.d.ts +37 -0
  33. package/dist/durable/durable-store-singleton.js +179 -0
  34. package/dist/durable/finding-impact.d.ts +30 -0
  35. package/dist/durable/finding-impact.js +53 -0
  36. package/dist/durable/in-memory-durable-store.d.ts +203 -0
  37. package/dist/durable/in-memory-durable-store.js +363 -0
  38. package/dist/durable/index.d.ts +5 -0
  39. package/dist/durable/index.js +4 -0
  40. package/dist/durable/pending-plan-store.d.ts +28 -0
  41. package/dist/durable/pending-plan-store.js +156 -0
  42. package/dist/durable/sqlite-durable-store.d.ts +71 -0
  43. package/dist/durable/sqlite-durable-store.js +631 -0
  44. package/dist/durable/types.d.ts +265 -0
  45. package/dist/durable/types.js +1 -0
  46. package/dist/handler/create-orchestrator.d.ts +4 -0
  47. package/dist/handler/create-orchestrator.js +85 -9
  48. package/dist/http/audio-actions.d.ts +1 -1
  49. package/dist/http/checks-actions.d.ts +39 -0
  50. package/dist/http/checks-actions.js +122 -0
  51. package/dist/http/history-actions.d.ts +1 -1
  52. package/dist/http/image-generate-actions.d.ts +2 -2
  53. package/dist/http/ops-actions.d.ts +2 -2
  54. package/dist/http/publish-actions.d.ts +4 -4
  55. package/dist/http/restore-actions.d.ts +3 -3
  56. package/dist/http/screenshot-actions.d.ts +2 -2
  57. package/dist/http/session-actions.d.ts +1 -1
  58. package/dist/http/telemetry-feedback-actions.d.ts +2 -2
  59. package/dist/http/unsplash-actions.d.ts +2 -2
  60. package/dist/http/variations-actions.d.ts +2 -2
  61. package/dist/index.d.ts +7 -0
  62. package/dist/index.js +27 -0
  63. package/dist/nlp/deterministic-planner-context.d.ts +16 -0
  64. package/dist/nlp/deterministic-planner-context.js +33 -7
  65. package/dist/nlp/deterministic-planner-suggestions.js +1 -1
  66. package/dist/nlp/plan-normalizer.js +193 -56
  67. package/dist/ops/destructive-action-gate.js +7 -2
  68. package/dist/ops/ops-engine.d.ts +12 -1
  69. package/dist/ops/ops-engine.js +41 -14
  70. package/dist/publish/publish-target-registry.js +1 -1
  71. package/dist/publish/publish-target.d.ts +1 -1
  72. package/dist/state/session-state.js +8 -1
  73. package/package.json +3 -3
@@ -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
- function rewriteRouteLinksInValue(input, fromSlug, toSlug) {
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 (typeof value === "string" && key.toLowerCase().includes("href")) {
113
- const mapped = remapRouteReference(value, fromSlug, toSlug);
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
- export function formatValidationError(reason) {
217
- return `${classifyGuardrailError(reason)}: ${reason}`;
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) === "schema_violation";
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 "./publish/publish-target-registry.js"
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 `./publish-target-registry.js`.
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.1",
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/migration-sdk": "^0.3.1",
27
- "@avocadostudio-ai/shared": "^0.3.1"
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",