@caelo-cms/shared 0.11.0 → 0.11.2
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/ai-tools.d.ts +32 -144
- package/dist/ai-tools.d.ts.map +1 -1
- package/dist/ai-tools.js +46 -139
- package/dist/ai-tools.js.map +1 -1
- package/dist/bug-intake.d.ts +46 -3
- package/dist/bug-intake.d.ts.map +1 -1
- package/dist/bug-intake.js +52 -3
- package/dist/bug-intake.js.map +1 -1
- package/dist/build-page.d.ts +5 -5
- package/dist/build-page.d.ts.map +1 -1
- package/dist/build-page.js +14 -9
- package/dist/build-page.js.map +1 -1
- package/dist/content.d.ts +14 -2
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +73 -15
- package/dist/content.js.map +1 -1
- package/dist/css-scope-scan.js +1 -1
- package/dist/css-var-scan.js +3 -3
- package/dist/css-var-scan.js.map +1 -1
- package/dist/escaped-line-breaks.d.ts +44 -0
- package/dist/escaped-line-breaks.d.ts.map +1 -0
- package/dist/escaped-line-breaks.js +203 -0
- package/dist/escaped-line-breaks.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/interactions.js +5 -5
- package/dist/interactions.js.map +1 -1
- package/dist/lists/default-types.d.ts +12 -0
- package/dist/lists/default-types.d.ts.map +1 -1
- package/dist/lists/default-types.js +36 -0
- package/dist/lists/default-types.js.map +1 -1
- package/dist/lists/fields.d.ts.map +1 -1
- package/dist/lists/fields.js +1 -0
- package/dist/lists/fields.js.map +1 -1
- package/dist/lists/render-data.d.ts +9 -0
- package/dist/lists/render-data.d.ts.map +1 -1
- package/dist/lists/render-deps.d.ts +4 -3
- package/dist/lists/render-deps.d.ts.map +1 -1
- package/dist/lists/render-deps.js +5 -3
- package/dist/lists/render-deps.js.map +1 -1
- package/dist/lists/resolve.d.ts +13 -0
- package/dist/lists/resolve.d.ts.map +1 -1
- package/dist/lists/resolve.js +95 -3
- package/dist/lists/resolve.js.map +1 -1
- package/dist/live-events.d.ts +44 -0
- package/dist/live-events.d.ts.map +1 -0
- package/dist/live-events.js +128 -0
- package/dist/live-events.js.map +1 -0
- package/dist/markup-structure.d.ts +20 -2
- package/dist/markup-structure.d.ts.map +1 -1
- package/dist/markup-structure.js +69 -3
- package/dist/markup-structure.js.map +1 -1
- package/dist/media.d.ts +15 -0
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js.map +1 -1
- package/dist/module-id-tag.d.ts +4 -1
- package/dist/module-id-tag.d.ts.map +1 -1
- package/dist/module-id-tag.js +11 -2
- package/dist/module-id-tag.js.map +1 -1
- package/dist/nested-module-render.d.ts +13 -0
- package/dist/nested-module-render.d.ts.map +1 -1
- package/dist/nested-module-render.js +34 -0
- package/dist/nested-module-render.js.map +1 -1
- package/dist/preview-compose.d.ts +18 -1
- package/dist/preview-compose.d.ts.map +1 -1
- package/dist/preview-compose.js +26 -7
- package/dist/preview-compose.js.map +1 -1
- package/dist/redirect-plan.d.ts +13 -0
- package/dist/redirect-plan.d.ts.map +1 -1
- package/dist/redirect-plan.js +22 -0
- package/dist/redirect-plan.js.map +1 -1
- package/dist/seo.d.ts +21 -0
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +32 -0
- package/dist/seo.js.map +1 -1
- package/dist/source-text-transport.d.ts +28 -2
- package/dist/source-text-transport.d.ts.map +1 -1
- package/dist/source-text-transport.js +12 -4
- package/dist/source-text-transport.js.map +1 -1
- package/dist/template-sections.d.ts.map +1 -1
- package/dist/template-sections.js +17 -4
- package/dist/template-sections.js.map +1 -1
- package/dist/theme-render.js +11 -26
- package/dist/theme-render.js.map +1 -1
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +12 -9
- package/dist/themes.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/redos-hardening.test.ts +1 -0
- package/src/ai-tools-add-module-modes.test.ts +1 -1
- package/src/ai-tools.test.ts +7 -20
- package/src/ai-tools.ts +57 -151
- package/src/bug-intake.test.ts +48 -1
- package/src/bug-intake.ts +58 -3
- package/src/build-page.ts +14 -9
- package/src/content.test.ts +26 -1
- package/src/content.ts +84 -16
- package/src/css-scope-scan.ts +1 -1
- package/src/css-var-scan.ts +3 -3
- package/src/escaped-line-breaks.ts +221 -0
- package/src/index.ts +2 -0
- package/src/interactions.ts +5 -5
- package/src/link-refs.test.ts +1 -0
- package/src/lists/default-types.ts +39 -0
- package/src/lists/fields.ts +1 -0
- package/src/lists/list-fixtures.test.ts +9 -3
- package/src/lists/render-data.ts +9 -0
- package/src/lists/render-deps.ts +6 -3
- package/src/lists/resolve.test.ts +182 -5
- package/src/lists/resolve.ts +101 -3
- package/src/live-events.ts +138 -0
- package/src/markup-structure.test.ts +47 -0
- package/src/markup-structure.ts +74 -3
- package/src/media.ts +12 -0
- package/src/module-id-tag.test.ts +38 -0
- package/src/module-id-tag.ts +11 -2
- package/src/nested-module-render.test.ts +39 -0
- package/src/nested-module-render.ts +35 -0
- package/src/preview-compose-deferrals.test.ts +44 -0
- package/src/preview-compose.ts +47 -10
- package/src/redirect-plan.test.ts +25 -0
- package/src/redirect-plan.ts +24 -0
- package/src/seo.test.ts +58 -0
- package/src/seo.ts +49 -0
- package/src/source-text-transport.test.ts +98 -3
- package/src/source-text-transport.ts +15 -4
- package/src/template-engine-lists.test.ts +55 -0
- package/src/template-sections.ts +18 -4
- package/src/theme-importers/__tests__/proto-pollution.test.ts +13 -3
- package/src/theme-render-sanitize.test.ts +8 -0
- package/src/theme-render.ts +11 -26
- package/src/themes.ts +12 -7
- package/src/version.ts +1 -1
package/dist/version.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
|
|
20
20
|
* follow standard SemVer.
|
|
21
21
|
*/
|
|
22
|
-
export declare const CAELO_VERSION = "0.11.
|
|
22
|
+
export declare const CAELO_VERSION = "0.11.2";
|
|
23
23
|
/**
|
|
24
24
|
* Deprecated alias for back-compat — early P17 work spelled this
|
|
25
25
|
* `CALEO_VERSION` (typo of "Caelo"). New code should import
|
|
@@ -31,7 +31,7 @@ export declare const CAELO_VERSION = "0.11.0";
|
|
|
31
31
|
*
|
|
32
32
|
* @deprecated use CAELO_VERSION
|
|
33
33
|
*/
|
|
34
|
-
export declare const CALEO_VERSION = "0.11.
|
|
34
|
+
export declare const CALEO_VERSION = "0.11.2";
|
|
35
35
|
/**
|
|
36
36
|
* Parsed shape — major/minor/patch + optional pre-release tag.
|
|
37
37
|
* Stable interface for callers that need to feature-gate (rare —
|
package/dist/version.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
|
|
21
21
|
* follow standard SemVer.
|
|
22
22
|
*/
|
|
23
|
-
export const CAELO_VERSION = "0.11.
|
|
23
|
+
export const CAELO_VERSION = "0.11.2";
|
|
24
24
|
/**
|
|
25
25
|
* Deprecated alias for back-compat — early P17 work spelled this
|
|
26
26
|
* `CALEO_VERSION` (typo of "Caelo"). New code should import
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/shared",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "Shared Zod schemas + types + the two-pass HTML composer used by every Caelo CMS workspace package. Stable enough for plugin authors and embedders to depend on directly.",
|
|
@@ -69,6 +69,7 @@ describe("renderTemplate sections (S4, epic #1107 tokenizer)", () => {
|
|
|
69
69
|
pages: new Map(),
|
|
70
70
|
media: new Map(),
|
|
71
71
|
siteLanguage: "en",
|
|
72
|
+
homePageId: null,
|
|
72
73
|
}).forPage(null);
|
|
73
74
|
const navField = { name: "nav_items", kind: "list" as const, listType: "links" };
|
|
74
75
|
const entry = (n: number, label: string, href: string) => ({
|
|
@@ -71,7 +71,7 @@ describe("addModuleToolInput modes (issue #159)", () => {
|
|
|
71
71
|
it("TOLERATES a moduleId call carrying authoring keys (incl. structural html) — placement-only, handler surfaces the ignored-authoring info", () => {
|
|
72
72
|
// §1A/§11 — a valid placement must never fail over an extra authoring
|
|
73
73
|
// field. moduleId + html passes; the tool handler (not the schema)
|
|
74
|
-
// reports that html was not applied and points at
|
|
74
|
+
// reports that html was not applied and points at update_modules_many.
|
|
75
75
|
const r = addModuleToolInput.safeParse({
|
|
76
76
|
...BASE,
|
|
77
77
|
moduleId: "11111111-1111-4111-8111-111111111102",
|
package/src/ai-tools.test.ts
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MPL-2.0
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from "bun:test";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
chatSendMessageInput,
|
|
7
|
-
editModuleToolInput,
|
|
8
|
-
siteMemoryProposeToolInput,
|
|
9
|
-
} from "./ai-tools.js";
|
|
4
|
+
import { chatPublishInput, chatSendMessageInput, siteMemoryProposeToolInput } from "./ai-tools.js";
|
|
5
|
+
import { moduleUpdateSchema } from "./content.js";
|
|
10
6
|
|
|
11
7
|
const UUID = "11111111-1111-4111-8111-111111111111";
|
|
12
8
|
const UUID_2 = "22222222-2222-4222-8222-222222222222";
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
// The per-item shape of update_modules_many (the module-edit tool extends it).
|
|
11
|
+
describe("moduleUpdateSchema (update_modules_many item)", () => {
|
|
15
12
|
it("accepts a minimal valid edit", () => {
|
|
16
|
-
const r =
|
|
13
|
+
const r = moduleUpdateSchema.safeParse({ moduleId: UUID, html: "<p>x</p>" });
|
|
17
14
|
expect(r.success).toBe(true);
|
|
18
15
|
});
|
|
19
16
|
|
|
20
17
|
it("rejects unknown keys (.strict)", () => {
|
|
21
|
-
const r =
|
|
18
|
+
const r = moduleUpdateSchema.safeParse({
|
|
22
19
|
moduleId: UUID,
|
|
23
20
|
html: "<p>x</p>",
|
|
24
21
|
pageId: UUID_2,
|
|
@@ -27,22 +24,12 @@ describe("editModuleToolInput", () => {
|
|
|
27
24
|
});
|
|
28
25
|
|
|
29
26
|
it("rejects oversized html", () => {
|
|
30
|
-
const r =
|
|
27
|
+
const r = moduleUpdateSchema.safeParse({
|
|
31
28
|
moduleId: UUID,
|
|
32
29
|
html: "x".repeat(300_000),
|
|
33
30
|
});
|
|
34
31
|
expect(r.success).toBe(false);
|
|
35
32
|
});
|
|
36
|
-
|
|
37
|
-
// e2e-livedit run 40351690739 — `{moduleId}` alone reached the op.
|
|
38
|
-
it("refuses an edit that changes nothing, naming what to pass", () => {
|
|
39
|
-
for (const input of [{ moduleId: UUID }, { moduleId: UUID, bindThemeLiterals: false }]) {
|
|
40
|
-
const r = editModuleToolInput.safeParse(input);
|
|
41
|
-
expect(r.success).toBe(false);
|
|
42
|
-
if (!r.success) expect(r.error.issues[0]?.message).toContain("edit_module changes nothing");
|
|
43
|
-
}
|
|
44
|
-
expect(editModuleToolInput.safeParse({ moduleId: UUID, kind: "hero" }).success).toBe(true);
|
|
45
|
-
});
|
|
46
33
|
});
|
|
47
34
|
|
|
48
35
|
describe("siteMemoryProposeToolInput", () => {
|
package/src/ai-tools.ts
CHANGED
|
@@ -17,15 +17,14 @@ import {
|
|
|
17
17
|
contentInstanceDeleteSchema,
|
|
18
18
|
contentInstanceUpdateSchema,
|
|
19
19
|
forkPlacementContentSchema,
|
|
20
|
-
MODULE_CSS_MAX,
|
|
21
|
-
MODULE_HTML_MAX,
|
|
22
|
-
MODULE_JS_MAX,
|
|
23
20
|
type ModuleField,
|
|
24
21
|
moduleFieldSchema,
|
|
22
|
+
pageShortTitleSchema,
|
|
25
23
|
pageTypeSlugSchema,
|
|
26
24
|
setPlacementContentSchema,
|
|
27
25
|
slugSchema,
|
|
28
26
|
} from "./content.js";
|
|
27
|
+
import type { SourceLanguage } from "./escaped-line-breaks.js";
|
|
29
28
|
import { LIST_ITEM_ATTR_RE } from "./lists/item-attr.js";
|
|
30
29
|
import { pluginPreviewSelectionSchema } from "./plugin-preview.js";
|
|
31
30
|
import { redirectTargetPathSchema } from "./redirect-target.js";
|
|
@@ -71,9 +70,10 @@ function stripSurroundingQuotes(v: unknown): unknown {
|
|
|
71
70
|
* is handled in one place instead of refused per tool.
|
|
72
71
|
*
|
|
73
72
|
* @param schema - the string schema (length caps, min) the decoded value must satisfy.
|
|
73
|
+
* @param language - the grammar of the value (`html`, `css`, `js`).
|
|
74
74
|
*/
|
|
75
|
-
export function sourceTextArg<T extends z.ZodType<string>>(schema: T) {
|
|
76
|
-
return z.preprocess(decodeSourceTextTransport, schema);
|
|
75
|
+
export function sourceTextArg<T extends z.ZodType<string>>(schema: T, language: SourceLanguage) {
|
|
76
|
+
return z.preprocess((value) => decodeSourceTextTransport(value, language), schema);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export const positionInputSchema = z.preprocess(
|
|
@@ -87,63 +87,6 @@ export const positionInputSchema = z.preprocess(
|
|
|
87
87
|
]),
|
|
88
88
|
);
|
|
89
89
|
|
|
90
|
-
export const editModuleToolInput = z
|
|
91
|
-
.object({
|
|
92
|
-
moduleId: z.string().uuid(),
|
|
93
|
-
displayName: z.string().min(1).max(128).optional(),
|
|
94
|
-
/**
|
|
95
|
-
* v0.12.0 — rewrite the module's purpose. Optional; passing it
|
|
96
|
-
* updates the `list_modules` catalog your future self will read.
|
|
97
|
-
*/
|
|
98
|
-
description: z.string().max(1000).optional(),
|
|
99
|
-
/** v0.12.0 — re-classify the module's role tag. */
|
|
100
|
-
kind: z.enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
|
|
101
|
-
/**
|
|
102
|
-
* v0.12.3 (issue #106) — re-classify the module's stable `type` (the
|
|
103
|
-
* class a parent's `allowedModuleTypes` whitelist matches against,
|
|
104
|
-
* e.g. `button`). Rarely needed; usually derived from displayName at
|
|
105
|
-
* create time. Set it to make this module satisfy a parent's allowlist.
|
|
106
|
-
*/
|
|
107
|
-
type: slugSchema.optional(),
|
|
108
|
-
html: sourceTextArg(z.string().max(MODULE_HTML_MAX)).optional(),
|
|
109
|
-
css: sourceTextArg(z.string().max(MODULE_CSS_MAX)).optional(),
|
|
110
|
-
js: sourceTextArg(z.string().max(MODULE_JS_MAX)).optional(),
|
|
111
|
-
/**
|
|
112
|
-
* v0.4.0 — module field schema. Each field declares one substitution
|
|
113
|
-
* slot referenced in the module HTML as `{{name}}`. Page placements
|
|
114
|
-
* fill these via `set_page_module_content`. Optional on edits — pass
|
|
115
|
-
* to replace the declared schema.
|
|
116
|
-
*/
|
|
117
|
-
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
118
|
-
/** issue #164 slice 2 — see addModuleToolInput.bindThemeLiterals. */
|
|
119
|
-
bindThemeLiterals: z.boolean().optional(),
|
|
120
|
-
})
|
|
121
|
-
.strict()
|
|
122
|
-
// e2e-livedit run 40351690739: a call with only a (placeholder)
|
|
123
|
-
// moduleId reached the op and came back as a bare "module not found".
|
|
124
|
-
// An edit that changes nothing is refused before any lookup, with the
|
|
125
|
-
// fields that make it one.
|
|
126
|
-
.refine((v) => Object.keys(v).some((k) => k !== "moduleId" && k !== "bindThemeLiterals"), {
|
|
127
|
-
message:
|
|
128
|
-
"edit_module changes nothing: pass what to change (html, css, js, fields, displayName, description, kind or type). list_modules shows the modules; add_module creates a new one.",
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* v0.4.0 — `set_page_module_content` AI tool. Fills the content values for
|
|
133
|
-
* a module placement on a specific page. Page-bound + branch-isolated per
|
|
134
|
-
* chat until publish (unlike `edit_module` which is global + immediate).
|
|
135
|
-
*/
|
|
136
|
-
export const setPageModuleContentToolInput = z
|
|
137
|
-
.object({
|
|
138
|
-
pageId: z.string().uuid(),
|
|
139
|
-
blockName: z.string().min(1).max(64),
|
|
140
|
-
position: z.number().int().nonnegative(),
|
|
141
|
-
/** Map of module field name → value. Fully replaces existing values. */
|
|
142
|
-
contentValues: z.record(z.string(), z.unknown()),
|
|
143
|
-
})
|
|
144
|
-
.strict();
|
|
145
|
-
export type SetPageModuleContentToolInput = z.infer<typeof setPageModuleContentToolInput>;
|
|
146
|
-
|
|
147
90
|
export const siteMemoryProposeToolInput = z
|
|
148
91
|
.object({
|
|
149
92
|
slot: z.enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
|
|
@@ -218,9 +161,9 @@ export const addModuleToolInput = z
|
|
|
218
161
|
description: z.string().max(1000).optional(),
|
|
219
162
|
kind: z.enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
|
|
220
163
|
type: slugSchema.optional(),
|
|
221
|
-
html: sourceTextArg(z.string().min(1).max(50_000)).optional(),
|
|
222
|
-
css: sourceTextArg(z.string().max(50_000)).optional(),
|
|
223
|
-
js: sourceTextArg(z.string().max(50_000)).optional(),
|
|
164
|
+
html: sourceTextArg(z.string().min(1).max(50_000), "html").optional(),
|
|
165
|
+
css: sourceTextArg(z.string().max(50_000), "css").optional(),
|
|
166
|
+
js: sourceTextArg(z.string().max(50_000), "js").optional(),
|
|
224
167
|
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
225
168
|
bindThemeLiterals: z.boolean().optional(),
|
|
226
169
|
/**
|
|
@@ -233,8 +176,8 @@ export const addModuleToolInput = z
|
|
|
233
176
|
* stores the values as the minted module's `fields[].default` —
|
|
234
177
|
* which requires explicit `fields` (moduleize field names are
|
|
235
178
|
* unknowable up front) and is rejected for `moduleId` reuse (a
|
|
236
|
-
* shared module renders its stored defaults;
|
|
237
|
-
* them).
|
|
179
|
+
* shared module renders its stored defaults; update_modules_many
|
|
180
|
+
* changes them).
|
|
238
181
|
*/
|
|
239
182
|
values: z.record(z.string(), z.unknown()).optional(),
|
|
240
183
|
})
|
|
@@ -245,7 +188,7 @@ export const addModuleToolInput = z
|
|
|
245
188
|
ctx.addIssue({
|
|
246
189
|
code: z.ZodIssueCode.custom,
|
|
247
190
|
message:
|
|
248
|
-
"Reusing a module on a layout renders the module's STORED field defaults — `values` has nothing to bind to. Drop `values` (the chrome shows the module as-is), or change the shared copy via
|
|
191
|
+
"Reusing a module on a layout renders the module's STORED field defaults — `values` has nothing to bind to. Drop `values` (the chrome shows the module as-is), or change the shared copy via update_modules_many.",
|
|
249
192
|
});
|
|
250
193
|
} else if (input.fields === undefined || input.fields.length === 0) {
|
|
251
194
|
ctx.addIssue({
|
|
@@ -269,7 +212,7 @@ export const addModuleToolInput = z
|
|
|
269
212
|
// (§1A/§11), so we do NOT reject a moduleId call that also carries authoring
|
|
270
213
|
// fields. Those fields are not applied here; the tool HANDLER surfaces an
|
|
271
214
|
// INFO for any ignored field whose value DIFFERS from the module's stored
|
|
272
|
-
// one, pointing at
|
|
215
|
+
// one, pointing at update_modules_many — so a dropped change is never silent (§2).
|
|
273
216
|
if (input.moduleId !== undefined) return;
|
|
274
217
|
if (input.displayName === undefined || input.html === undefined) {
|
|
275
218
|
ctx.addIssue({
|
|
@@ -361,44 +304,6 @@ export const deleteContentInstanceToolInput = contentInstanceDeleteSchema;
|
|
|
361
304
|
export const setPlacementContentToolInput = setPlacementContentSchema;
|
|
362
305
|
export const forkPlacementContentToolInput = forkPlacementContentSchema;
|
|
363
306
|
|
|
364
|
-
export const AI_TOOLS = [
|
|
365
|
-
"edit_module",
|
|
366
|
-
"site_memory_propose",
|
|
367
|
-
"add_module",
|
|
368
|
-
"create_page",
|
|
369
|
-
// audit #3 — page metadata (name/title/slug/template/status) is one tool for
|
|
370
|
-
// 1..200 pages: `update_pages_many`. The former rename_page / set_page_title
|
|
371
|
-
// / change_page_slug were single-field wrappers over the same pages.update.
|
|
372
|
-
"update_pages_many",
|
|
373
|
-
// audit #4 — one delete tool for 1..200 pages, disposition per page.
|
|
374
|
-
// The former singular delete_page folded in (n=1 is a one-item array).
|
|
375
|
-
"delete_pages_many",
|
|
376
|
-
// one remove-module tool routed by target (page|layout).
|
|
377
|
-
"remove_module_from",
|
|
378
|
-
"update_theme",
|
|
379
|
-
"set_template_layout",
|
|
380
|
-
"create_layout",
|
|
381
|
-
"set_site_defaults",
|
|
382
|
-
"duplicate_page",
|
|
383
|
-
"repoint_page_template",
|
|
384
|
-
"move_module",
|
|
385
|
-
"reorder_module",
|
|
386
|
-
"set_nav_menu",
|
|
387
|
-
"add_plugin_to_page",
|
|
388
|
-
// v0.12.0 — content_instances + placement binding
|
|
389
|
-
"list_content_instances",
|
|
390
|
-
"get_content_instance",
|
|
391
|
-
"create_content_instance",
|
|
392
|
-
"set_content_instance_values",
|
|
393
|
-
"delete_content_instance",
|
|
394
|
-
"set_placement_content",
|
|
395
|
-
"fork_placement_content",
|
|
396
|
-
// issue #299 — bulk-first build path (§11)
|
|
397
|
-
"build_page",
|
|
398
|
-
"create_content_instances",
|
|
399
|
-
"set_page_module_content_many",
|
|
400
|
-
] as const;
|
|
401
|
-
export type AiToolName = (typeof AI_TOOLS)[number];
|
|
402
307
|
export type AddPluginToPageToolInput = z.infer<typeof addPluginToPageToolInput>;
|
|
403
308
|
export type ListContentInstancesToolInput = z.infer<typeof listContentInstancesToolInput>;
|
|
404
309
|
export type GetContentInstanceToolInput = z.infer<typeof getContentInstanceToolInput>;
|
|
@@ -473,7 +378,7 @@ export const CHAT_MAX_ATTACHMENTS = 4;
|
|
|
473
378
|
* `position` / `field` (#917) — plus `item` / `key` for an entry of a
|
|
474
379
|
* local list field (epic #1107) — name the placement field the text
|
|
475
380
|
* comes from when the preview marked it, so the AI can address it in
|
|
476
|
-
* `
|
|
381
|
+
* `set_page_module_content_many` without inspecting the page first.
|
|
477
382
|
* `listItem` is the `data-caelo-list-item` of the list entry the element
|
|
478
383
|
* sits in (epic #1107, `docs/lists.md` §13): the chip then names that
|
|
479
384
|
* entry — of a shared list, or of a module's own list — not just the
|
|
@@ -676,8 +581,8 @@ export const createTemplateToolInput = z
|
|
|
676
581
|
.object({
|
|
677
582
|
slug: slugInputSchema,
|
|
678
583
|
displayName: z.string().min(1).max(256),
|
|
679
|
-
html: sourceTextArg(z.string().min(1).max(2_000_000)),
|
|
680
|
-
css: sourceTextArg(z.string().max(2_000_000)).default(""),
|
|
584
|
+
html: sourceTextArg(z.string().min(1).max(2_000_000), "html"),
|
|
585
|
+
css: sourceTextArg(z.string().max(2_000_000), "css").default(""),
|
|
681
586
|
layoutId: z.string().uuid().optional(),
|
|
682
587
|
/** Epic #1107 — the page type (slug) this template serves; omit for the site default. */
|
|
683
588
|
pageType: pageTypeSlugSchema.optional(),
|
|
@@ -728,8 +633,8 @@ export const createLayoutToolInput = z
|
|
|
728
633
|
.object({
|
|
729
634
|
slug: slugInputSchema,
|
|
730
635
|
displayName: z.string().min(1).max(200),
|
|
731
|
-
html: sourceTextArg(z.string().min(1).max(50_000)),
|
|
732
|
-
css: sourceTextArg(z.string().max(50_000)).optional(),
|
|
636
|
+
html: sourceTextArg(z.string().min(1).max(50_000), "html"),
|
|
637
|
+
css: sourceTextArg(z.string().max(50_000), "css").optional(),
|
|
733
638
|
blocks: z
|
|
734
639
|
.array(
|
|
735
640
|
z
|
|
@@ -765,8 +670,9 @@ export const duplicatePageToolInput = z
|
|
|
765
670
|
.object({
|
|
766
671
|
sourcePageId: z.string().uuid(),
|
|
767
672
|
newSlug: slugInputSchema,
|
|
768
|
-
newName: z.string().min(1).max(256).optional(),
|
|
769
673
|
newTitle: z.string().min(1).max(256).optional(),
|
|
674
|
+
/** #1142 — the copy's breadcrumb label; omitted = newTitle when given, else the source's. */
|
|
675
|
+
newShortTitle: pageShortTitleSchema.optional(),
|
|
770
676
|
targetTemplateId: z.string().uuid().optional(),
|
|
771
677
|
/** Where the copy sits: a parent page id, or null for the top level.
|
|
772
678
|
* Omitted = next to the source (same parent). */
|
|
@@ -794,32 +700,50 @@ export const repointPageTemplateToolInput = z
|
|
|
794
700
|
})
|
|
795
701
|
.strict();
|
|
796
702
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
703
|
+
/**
|
|
704
|
+
* `move_module` position: `top` | `bottom` | a 0-based index in the target
|
|
705
|
+
* block, plus `up` / `down` (one slot within the module's CURRENT block).
|
|
706
|
+
* Same quote-stripping + numeric-string coercion as `positionInputSchema`.
|
|
707
|
+
*/
|
|
708
|
+
export const movePositionInputSchema = z.preprocess(
|
|
709
|
+
stripSurroundingQuotes,
|
|
710
|
+
z.union([
|
|
711
|
+
z.enum(["top", "bottom", "up", "down"]),
|
|
712
|
+
z.preprocess(
|
|
713
|
+
(v) => (typeof v === "string" && /^\d+$/.test(v) ? Number.parseInt(v, 10) : v),
|
|
714
|
+
z.number().int().min(0).max(1000),
|
|
715
|
+
),
|
|
716
|
+
]),
|
|
717
|
+
);
|
|
806
718
|
|
|
807
|
-
|
|
719
|
+
/**
|
|
720
|
+
* `move_module` — moves a placement to `toBlockName` (omitted = the block it
|
|
721
|
+
* is in now, i.e. a reorder) at `position`. `up` / `down` only make sense
|
|
722
|
+
* inside the current block; the handler refuses them with another block.
|
|
723
|
+
*/
|
|
724
|
+
export const moveModuleToolInput = z
|
|
808
725
|
.object({
|
|
809
726
|
pageId: z.string().uuid(),
|
|
810
727
|
moduleId: z.string().uuid(),
|
|
811
728
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
729
|
+
* #1195 — which placement of `moduleId` to move when the page holds it
|
|
730
|
+
* more than once: its block and 0-based position there. Needed only
|
|
731
|
+
* then; the error names the placements.
|
|
814
732
|
*/
|
|
815
|
-
|
|
733
|
+
fromBlockName: z.string().min(1).max(80).optional(),
|
|
734
|
+
fromPosition: z.preprocess(
|
|
735
|
+
(v) => (typeof v === "string" && /^\d+$/.test(v) ? Number.parseInt(v, 10) : v),
|
|
736
|
+
z.number().int().min(0).max(1000).optional(),
|
|
737
|
+
),
|
|
738
|
+
toBlockName: z.string().min(1).max(80).optional(),
|
|
739
|
+
position: movePositionInputSchema,
|
|
816
740
|
})
|
|
817
741
|
.strict();
|
|
818
742
|
|
|
819
743
|
/**
|
|
820
744
|
* P7 — `find_media`. Searches the media library by alt-text /
|
|
821
745
|
* filename / mime. Each row carries the media UUID (what
|
|
822
|
-
* `set_theme_asset` / `
|
|
746
|
+
* `set_theme_asset` / `set_media_alt_many` require) plus a pre-resolved
|
|
823
747
|
* variant URL. The AI's only media-inventory surface — the `## Media`
|
|
824
748
|
* system-prompt block was removed by the context diet (#300), so every
|
|
825
749
|
* asset lookup ("an image of a sunlit office") goes through this tool.
|
|
@@ -864,7 +788,7 @@ export const regenerateMediaVariantsToolInput = z
|
|
|
864
788
|
export type RegenerateMediaVariantsToolInput = z.infer<typeof regenerateMediaVariantsToolInput>;
|
|
865
789
|
|
|
866
790
|
/**
|
|
867
|
-
* P7 — `
|
|
791
|
+
* P7 — one item of `set_media_alt_many`. AI may improve a11y on an existing asset
|
|
868
792
|
* without a human round-trip (e.g. when the editor uploaded an image
|
|
869
793
|
* with the default alt and the AI reads its content). Updates only
|
|
870
794
|
* the alt field on `media_assets`.
|
|
@@ -894,7 +818,7 @@ export const setNotFoundPageToolInput = z.object({ pageId: z.string().uuid().nul
|
|
|
894
818
|
export type SetNotFoundPageToolInput = z.infer<typeof setNotFoundPageToolInput>;
|
|
895
819
|
|
|
896
820
|
/**
|
|
897
|
-
* 0181 — `
|
|
821
|
+
* 0181 — one item of `set_media_source_many`. AI records an asset's provenance (source
|
|
898
822
|
* kind + origin detail) and licence when known. All fields but the
|
|
899
823
|
* assetId are optional; the op COALESCEs so omitted fields stay
|
|
900
824
|
* unchanged.
|
|
@@ -910,7 +834,7 @@ export const setMediaSourceToolInput = z
|
|
|
910
834
|
export type SetMediaSourceToolInput = z.infer<typeof setMediaSourceToolInput>;
|
|
911
835
|
|
|
912
836
|
/**
|
|
913
|
-
* P8 — `
|
|
837
|
+
* P8 — one item of `set_page_seo_many`. Manual / panel writes to the per-page SEO
|
|
914
838
|
* sidecar. AI calls this only on explicit user intent
|
|
915
839
|
* ("set the home meta description to ..."). Doesn't bump fingerprints
|
|
916
840
|
* (autofilled_at / optimized_at).
|
|
@@ -931,7 +855,7 @@ export const setPageSeoToolInput = z
|
|
|
931
855
|
export type SetPageSeoToolInput = z.infer<typeof setPageSeoToolInput>;
|
|
932
856
|
|
|
933
857
|
/**
|
|
934
|
-
* P8 —
|
|
858
|
+
* P8 — one item of `autofill_page_seo_many`.
|
|
935
859
|
* Fill-once. Refuses when the page's SEO was already autofilled. The AI
|
|
936
860
|
* calls it before a page's first Stage; staging and production builds
|
|
937
861
|
* refuse pages without a meta description (deploy/missing-seo.ts).
|
|
@@ -945,22 +869,6 @@ export const autofillPageSeoToolInput = z
|
|
|
945
869
|
.strict();
|
|
946
870
|
export type AutofillPageSeoToolInput = z.infer<typeof autofillPageSeoToolInput>;
|
|
947
871
|
|
|
948
|
-
/**
|
|
949
|
-
* P8 — `optimize_page_seo`. Explicit re-optimization. Always allowed.
|
|
950
|
-
* Takes optional context (keyword analysis / intent shifts / branding
|
|
951
|
-
* changes). The AI can call this across N pages in one chat turn —
|
|
952
|
-
* the resulting changes batch into one Publish-pill confirm.
|
|
953
|
-
*/
|
|
954
|
-
export const optimizePageSeoToolInput = z
|
|
955
|
-
.object({
|
|
956
|
-
pageId: z.string().uuid(),
|
|
957
|
-
metaDescription: z.string().min(1).max(320),
|
|
958
|
-
ogImageAssetId: z.string().uuid().nullable().optional(),
|
|
959
|
-
context: z.string().max(4000).optional(),
|
|
960
|
-
})
|
|
961
|
-
.strict();
|
|
962
|
-
export type OptimizePageSeoToolInput = z.infer<typeof optimizePageSeoToolInput>;
|
|
963
|
-
|
|
964
872
|
/**
|
|
965
873
|
* P8 AI-first review pass — bulk variants. Per CLAUDE.md §11, bulk
|
|
966
874
|
* tools save round-trips when the AI knows it has N changes to make
|
|
@@ -980,7 +888,7 @@ export type FindRedirectsToolInput = z.infer<typeof findRedirectsToolInput>;
|
|
|
980
888
|
|
|
981
889
|
export const bulkCreateRedirectsToolInput = z
|
|
982
890
|
.object({
|
|
983
|
-
|
|
891
|
+
items: z
|
|
984
892
|
.array(
|
|
985
893
|
z
|
|
986
894
|
.object({
|
|
@@ -1032,7 +940,7 @@ export type BulkDeleteRedirectsToolInput = z.infer<typeof bulkDeleteRedirectsToo
|
|
|
1032
940
|
|
|
1033
941
|
export const bulkOptimizeSeoToolInput = z
|
|
1034
942
|
.object({
|
|
1035
|
-
|
|
943
|
+
items: z
|
|
1036
944
|
.array(
|
|
1037
945
|
z
|
|
1038
946
|
.object({
|
|
@@ -1049,7 +957,6 @@ export const bulkOptimizeSeoToolInput = z
|
|
|
1049
957
|
.strict();
|
|
1050
958
|
export type BulkOptimizeSeoToolInput = z.infer<typeof bulkOptimizeSeoToolInput>;
|
|
1051
959
|
|
|
1052
|
-
export type EditModuleToolInput = z.infer<typeof editModuleToolInput>;
|
|
1053
960
|
export type SiteMemoryProposeToolInput = z.infer<typeof siteMemoryProposeToolInput>;
|
|
1054
961
|
export type CreateTemplateToolInput = z.infer<typeof createTemplateToolInput>;
|
|
1055
962
|
// v0.10.22 — `UpdateThemeToolInput` removed alongside `update_theme` tool.
|
|
@@ -1067,7 +974,6 @@ export type SetSiteDefaultsToolInput = z.infer<typeof setSiteDefaultsToolInput>;
|
|
|
1067
974
|
export type DuplicatePageToolInput = z.infer<typeof duplicatePageToolInput>;
|
|
1068
975
|
export type RepointPageTemplateToolInput = z.infer<typeof repointPageTemplateToolInput>;
|
|
1069
976
|
export type MoveModuleToolInput = z.infer<typeof moveModuleToolInput>;
|
|
1070
|
-
export type ReorderModuleToolInput = z.infer<typeof reorderModuleToolInput>;
|
|
1071
977
|
// v0.10.22 — `SetNavMenuToolInput` removed alongside `set_nav_menu` tool.
|
|
1072
978
|
|
|
1073
979
|
/**
|
package/src/bug-intake.test.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it } from "bun:test";
|
|
11
11
|
import {
|
|
12
|
+
BUG_REPORT_KIND_BEFORE_1192,
|
|
12
13
|
bugIntakeReport,
|
|
13
14
|
bugIntakeSigningString,
|
|
14
15
|
computeBugFingerprint,
|
|
@@ -32,7 +33,7 @@ describe("computeBugFingerprint", () => {
|
|
|
32
33
|
});
|
|
33
34
|
|
|
34
35
|
it("keeps different tools and different quoted field names apart", async () => {
|
|
35
|
-
expect(await auto("boom", "build_page")).not.toBe(await auto("boom", "
|
|
36
|
+
expect(await auto("boom", "build_page")).not.toBe(await auto("boom", "update_modules_many"));
|
|
36
37
|
expect(await auto('Invalid input at "page.slug"')).not.toBe(
|
|
37
38
|
await auto('Invalid input at "page.title"'),
|
|
38
39
|
);
|
|
@@ -152,3 +153,49 @@ describe("bugIntakeReport", () => {
|
|
|
152
153
|
expect(parsed.success).toBe(false);
|
|
153
154
|
});
|
|
154
155
|
});
|
|
156
|
+
|
|
157
|
+
describe("bugIntakeReport kind (#1192)", () => {
|
|
158
|
+
const base = {
|
|
159
|
+
protocolVersion: 1,
|
|
160
|
+
reportId: "7f3c2b9e-1d4a-4e8b-9c0f-2a1b3c4d5e6f",
|
|
161
|
+
caeloVersion: "0.10.55",
|
|
162
|
+
fingerprint: "a".repeat(64),
|
|
163
|
+
source: "ai",
|
|
164
|
+
severity: "degraded",
|
|
165
|
+
blockedTask: false,
|
|
166
|
+
suspectedTool: null,
|
|
167
|
+
title: "t",
|
|
168
|
+
whatHappened: "w",
|
|
169
|
+
expected: "e",
|
|
170
|
+
evidence: null,
|
|
171
|
+
} as const;
|
|
172
|
+
|
|
173
|
+
it("parses a report without kind, as an install from before #1192 sends it, as a defect", () => {
|
|
174
|
+
expect(BUG_REPORT_KIND_BEFORE_1192).toBe("defect");
|
|
175
|
+
const parsed = bugIntakeReport.parse(base);
|
|
176
|
+
expect(parsed.kind).toBe("defect");
|
|
177
|
+
expect(parsed.severity).toBe("degraded");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("round-trips a suggestion, which carries no severity", () => {
|
|
181
|
+
const sent = { ...base, kind: "suggestion", severity: null };
|
|
182
|
+
const parsed = bugIntakeReport.parse(sent);
|
|
183
|
+
expect(parsed).toEqual(sent);
|
|
184
|
+
expect(bugIntakeReport.parse(JSON.parse(JSON.stringify(parsed)))).toEqual(sent);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("refuses a defect without severity, a graded suggestion and an auto-captured suggestion", () => {
|
|
188
|
+
const issues = (r: unknown) =>
|
|
189
|
+
bugIntakeReport.safeParse(r).error?.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
190
|
+
expect(issues({ ...base, kind: "defect", severity: null })).toEqual([
|
|
191
|
+
"severity: a defect needs a severity",
|
|
192
|
+
]);
|
|
193
|
+
expect(issues({ ...base, kind: "suggestion" })).toEqual([
|
|
194
|
+
"severity: a suggestion has no severity; send null",
|
|
195
|
+
]);
|
|
196
|
+
expect(issues({ ...base, source: "auto", kind: "suggestion", severity: null })).toEqual([
|
|
197
|
+
"kind: an auto-captured report is always a defect",
|
|
198
|
+
]);
|
|
199
|
+
expect(issues({ ...base, kind: "feature" })?.[0]).toStartWith("kind:");
|
|
200
|
+
});
|
|
201
|
+
});
|
package/src/bug-intake.ts
CHANGED
|
@@ -60,7 +60,41 @@ export const bugIntakeRegisterRequest = z
|
|
|
60
60
|
.strict();
|
|
61
61
|
export type BugIntakeRegisterRequest = z.infer<typeof bugIntakeRegisterRequest>;
|
|
62
62
|
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* What a report is about (#1192).
|
|
65
|
+
* defect — something in Caelo misbehaved: a tool contradicted its
|
|
66
|
+
* description, a render contradicted stored state, a call
|
|
67
|
+
* failed. Auto captures and provider errors are always this.
|
|
68
|
+
* suggestion — nothing misbehaved, but Caelo lacks something the AI needed:
|
|
69
|
+
* a capability, a tool, a docs page. Filed by the AI only.
|
|
70
|
+
* The intake keeps the two apart end to end: own title prefix and label, own
|
|
71
|
+
* daily issue cap, and matching never puts one kind into the other's cluster.
|
|
72
|
+
*/
|
|
73
|
+
export const bugReportKind = z.enum(["defect", "suggestion"]);
|
|
74
|
+
export type BugReportKind = z.infer<typeof bugReportKind>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The kind of a report that names none. Contract rule (#1192): installs
|
|
78
|
+
* released before `kind` existed send no kind, and everything they could
|
|
79
|
+
* file was a defect, so a missing kind IS `defect`. This is a protocol rule,
|
|
80
|
+
* not a guess: it holds for every protocol-1 report, and the intake applies
|
|
81
|
+
* the same rule to the records it stored before #1192.
|
|
82
|
+
*/
|
|
83
|
+
export const BUG_REPORT_KIND_BEFORE_1192: BugReportKind = "defect";
|
|
84
|
+
|
|
85
|
+
/** How badly a defect hurt the task. Defects only: a suggestion has no severity. */
|
|
86
|
+
export const bugReportSeverity = z.enum(["blocking", "degraded", "cosmetic"]);
|
|
87
|
+
export type BugReportSeverity = z.infer<typeof bugReportSeverity>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* `POST /v1/reports` — one redacted bug report (signed).
|
|
91
|
+
*
|
|
92
|
+
* `kind` is optional on the wire only for the contract rule above; parsing
|
|
93
|
+
* always yields one. `severity` is set for a defect and `null` for a
|
|
94
|
+
* suggestion (nothing broke, so there is nothing to grade), and an
|
|
95
|
+
* auto-captured report is always a defect. A report that breaks either is
|
|
96
|
+
* refused, not repaired: it came from a broken client.
|
|
97
|
+
*/
|
|
64
98
|
export const bugIntakeReport = z
|
|
65
99
|
.object({
|
|
66
100
|
protocolVersion: z.literal(BUG_INTAKE_PROTOCOL_VERSION),
|
|
@@ -69,7 +103,8 @@ export const bugIntakeReport = z
|
|
|
69
103
|
caeloVersion: z.string().min(1).max(64),
|
|
70
104
|
fingerprint: fingerprintSchema,
|
|
71
105
|
source: z.enum(["ai", "auto"]),
|
|
72
|
-
|
|
106
|
+
kind: bugReportKind.default(BUG_REPORT_KIND_BEFORE_1192),
|
|
107
|
+
severity: bugReportSeverity.nullable(),
|
|
73
108
|
blockedTask: z.boolean(),
|
|
74
109
|
suspectedTool: z.string().max(200).nullable(),
|
|
75
110
|
title: z.string().min(1).max(BUG_INTAKE_LIMITS.title),
|
|
@@ -77,7 +112,27 @@ export const bugIntakeReport = z
|
|
|
77
112
|
expected: z.string().min(1).max(BUG_INTAKE_LIMITS.expected),
|
|
78
113
|
evidence: z.string().max(BUG_INTAKE_LIMITS.evidence).nullable(),
|
|
79
114
|
})
|
|
80
|
-
.strict()
|
|
115
|
+
.strict()
|
|
116
|
+
.superRefine((r, ctx) => {
|
|
117
|
+
if (r.kind === "defect" && r.severity === null) {
|
|
118
|
+
ctx.addIssue({ code: "custom", path: ["severity"], message: "a defect needs a severity" });
|
|
119
|
+
}
|
|
120
|
+
if (r.kind === "suggestion" && r.severity !== null) {
|
|
121
|
+
ctx.addIssue({
|
|
122
|
+
code: "custom",
|
|
123
|
+
path: ["severity"],
|
|
124
|
+
message: "a suggestion has no severity; send null",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (r.kind === "suggestion" && r.source === "auto") {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: "custom",
|
|
130
|
+
path: ["kind"],
|
|
131
|
+
message: "an auto-captured report is always a defect",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
/** A parsed report: `kind` is always present. */
|
|
81
136
|
export type BugIntakeReport = z.infer<typeof bugIntakeReport>;
|
|
82
137
|
|
|
83
138
|
/**
|