@caelo-cms/shared 0.10.22 → 0.10.24
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 +289 -273
- package/dist/ai-tools.d.ts.map +1 -1
- package/dist/ai-tools.js +342 -323
- package/dist/ai-tools.js.map +1 -1
- package/dist/auth-forms.d.ts.map +1 -1
- package/dist/auth-forms.js +4 -1
- package/dist/auth-forms.js.map +1 -1
- package/dist/base-css.d.ts +35 -0
- package/dist/base-css.d.ts.map +1 -0
- package/dist/base-css.js +40 -0
- package/dist/base-css.js.map +1 -0
- package/dist/build-page.d.ts +330 -0
- package/dist/build-page.d.ts.map +1 -0
- package/dist/build-page.js +282 -0
- package/dist/build-page.js.map +1 -0
- package/dist/content.d.ts +322 -9
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +354 -11
- package/dist/content.js.map +1 -1
- package/dist/css-gradient-scan.d.ts +14 -0
- package/dist/css-gradient-scan.d.ts.map +1 -0
- package/dist/css-gradient-scan.js +81 -0
- package/dist/css-gradient-scan.js.map +1 -0
- package/dist/css-var-scan.d.ts +56 -0
- package/dist/css-var-scan.d.ts.map +1 -0
- package/dist/css-var-scan.js +97 -0
- package/dist/css-var-scan.js.map +1 -0
- package/dist/design-draft-shell.d.ts +21 -0
- package/dist/design-draft-shell.d.ts.map +1 -0
- package/dist/design-draft-shell.js +81 -0
- package/dist/design-draft-shell.js.map +1 -0
- package/dist/design-manifest.d.ts +36 -0
- package/dist/design-manifest.d.ts.map +1 -0
- package/dist/design-manifest.js +90 -0
- package/dist/design-manifest.js.map +1 -0
- package/dist/fonts.d.ts +89 -0
- package/dist/fonts.d.ts.map +1 -0
- package/dist/fonts.js +241 -0
- package/dist/fonts.js.map +1 -0
- package/dist/genesis-inventory.d.ts +32 -0
- package/dist/genesis-inventory.d.ts.map +1 -0
- package/dist/genesis-inventory.js +186 -0
- package/dist/genesis-inventory.js.map +1 -0
- package/dist/genesis.d.ts +102 -0
- package/dist/genesis.d.ts.map +1 -0
- package/dist/genesis.js +145 -0
- package/dist/genesis.js.map +1 -0
- package/dist/i18n.d.ts +29 -36
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +53 -128
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/interactions.d.ts +23 -0
- package/dist/interactions.d.ts.map +1 -0
- package/dist/interactions.js +44 -0
- package/dist/interactions.js.map +1 -0
- package/dist/media.d.ts +101 -16
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +126 -15
- package/dist/media.js.map +1 -1
- package/dist/page-log.d.ts +94 -0
- package/dist/page-log.d.ts.map +1 -0
- package/dist/page-log.js +111 -0
- package/dist/page-log.js.map +1 -0
- package/dist/preview-compose.d.ts +85 -12
- package/dist/preview-compose.d.ts.map +1 -1
- package/dist/preview-compose.js +157 -67
- package/dist/preview-compose.js.map +1 -1
- package/dist/proposal-status.d.ts +40 -0
- package/dist/proposal-status.d.ts.map +1 -0
- package/dist/proposal-status.js +34 -0
- package/dist/proposal-status.js.map +1 -0
- package/dist/responsive-images.d.ts +64 -0
- package/dist/responsive-images.d.ts.map +1 -0
- package/dist/responsive-images.js +98 -0
- package/dist/responsive-images.js.map +1 -0
- package/dist/safe-keys.d.ts +9 -0
- package/dist/safe-keys.d.ts.map +1 -0
- package/dist/safe-keys.js +20 -0
- package/dist/safe-keys.js.map +1 -0
- package/dist/seo.d.ts +11 -16
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +7 -23
- package/dist/seo.js.map +1 -1
- package/dist/skills.d.ts +14 -68
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +19 -113
- package/dist/skills.js.map +1 -1
- package/dist/strip-cdata.d.ts +7 -0
- package/dist/strip-cdata.d.ts.map +1 -0
- package/dist/strip-cdata.js +48 -0
- package/dist/strip-cdata.js.map +1 -0
- package/dist/structured-sets.d.ts +6 -52
- package/dist/structured-sets.d.ts.map +1 -1
- package/dist/structured-sets.js +6 -68
- package/dist/structured-sets.js.map +1 -1
- package/dist/subagents.d.ts +105 -3
- package/dist/subagents.d.ts.map +1 -1
- package/dist/subagents.js +224 -41
- package/dist/subagents.js.map +1 -1
- package/dist/template-engine.d.ts +85 -0
- package/dist/template-engine.d.ts.map +1 -0
- package/dist/template-engine.js +403 -0
- package/dist/template-engine.js.map +1 -0
- package/dist/theme-importers/auto-detect.d.ts +26 -0
- package/dist/theme-importers/auto-detect.d.ts.map +1 -0
- package/dist/theme-importers/auto-detect.js +42 -0
- package/dist/theme-importers/auto-detect.js.map +1 -0
- package/dist/theme-importers/css-comments.d.ts +12 -0
- package/dist/theme-importers/css-comments.d.ts.map +1 -0
- package/dist/theme-importers/css-comments.js +15 -0
- package/dist/theme-importers/css-comments.js.map +1 -0
- package/dist/theme-importers/dtcg.d.ts +46 -0
- package/dist/theme-importers/dtcg.d.ts.map +1 -0
- package/dist/theme-importers/dtcg.js +111 -0
- package/dist/theme-importers/dtcg.js.map +1 -0
- package/dist/theme-importers/loose.d.ts +3 -0
- package/dist/theme-importers/loose.d.ts.map +1 -0
- package/dist/theme-importers/loose.js +76 -0
- package/dist/theme-importers/loose.js.map +1 -0
- package/dist/theme-importers/shadcn.d.ts +24 -0
- package/dist/theme-importers/shadcn.d.ts.map +1 -0
- package/dist/theme-importers/shadcn.js +135 -0
- package/dist/theme-importers/shadcn.js.map +1 -0
- package/dist/theme-importers/style-dictionary.d.ts +17 -0
- package/dist/theme-importers/style-dictionary.d.ts.map +1 -0
- package/dist/theme-importers/style-dictionary.js +125 -0
- package/dist/theme-importers/style-dictionary.js.map +1 -0
- package/dist/theme-importers/tailwind.d.ts +3 -0
- package/dist/theme-importers/tailwind.d.ts.map +1 -0
- package/dist/theme-importers/tailwind.js +218 -0
- package/dist/theme-importers/tailwind.js.map +1 -0
- package/dist/theme-literal-binding.d.ts +37 -0
- package/dist/theme-literal-binding.d.ts.map +1 -0
- package/dist/theme-literal-binding.js +138 -0
- package/dist/theme-literal-binding.js.map +1 -0
- package/dist/theme-normalize.d.ts +31 -0
- package/dist/theme-normalize.d.ts.map +1 -0
- package/dist/theme-normalize.js +587 -0
- package/dist/theme-normalize.js.map +1 -0
- package/dist/theme-ramp.d.ts +55 -0
- package/dist/theme-ramp.d.ts.map +1 -0
- package/dist/theme-ramp.js +149 -0
- package/dist/theme-ramp.js.map +1 -0
- package/dist/theme-render.d.ts +105 -0
- package/dist/theme-render.d.ts.map +1 -0
- package/dist/theme-render.js +441 -0
- package/dist/theme-render.js.map +1 -0
- package/dist/themes-errors.d.ts +109 -0
- package/dist/themes-errors.d.ts.map +1 -0
- package/dist/themes-errors.js +170 -0
- package/dist/themes-errors.js.map +1 -0
- package/dist/themes.d.ts +343 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +697 -0
- package/dist/themes.js.map +1 -0
- package/dist/version.d.ts +7 -4
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +6 -3
- package/dist/version.js.map +1 -1
- package/package.json +10 -2
- package/src/__tests__/redos-hardening.test.ts +160 -0
- package/src/ai-tools-add-module-modes.test.ts +106 -0
- package/src/ai-tools-position.test.ts +134 -0
- package/src/ai-tools.test.ts +81 -0
- package/src/ai-tools.ts +1105 -0
- package/src/auth-forms.ts +36 -0
- package/src/base-css.ts +42 -0
- package/src/build-page.test.ts +228 -0
- package/src/build-page.ts +319 -0
- package/src/cap-failures.ts +67 -0
- package/src/content.test.ts +170 -0
- package/src/content.ts +620 -0
- package/src/context.ts +43 -0
- package/src/css-gradient-scan.ts +88 -0
- package/src/css-var-scan.test.ts +96 -0
- package/src/css-var-scan.ts +144 -0
- package/src/derive-module-type.test.ts +80 -0
- package/src/design-draft-shell.test.ts +85 -0
- package/src/design-draft-shell.ts +109 -0
- package/src/design-manifest.ts +93 -0
- package/src/fonts.test.ts +157 -0
- package/src/fonts.ts +296 -0
- package/src/genesis-inventory.test.ts +86 -0
- package/src/genesis-inventory.ts +215 -0
- package/src/genesis-sanitize.test.ts +35 -0
- package/src/genesis.ts +158 -0
- package/src/i18n.test.ts +58 -0
- package/src/i18n.ts +91 -0
- package/src/index.test.ts +10 -0
- package/src/index.ts +59 -0
- package/src/interactions.ts +48 -0
- package/src/logger.ts +147 -0
- package/src/media.test.ts +160 -0
- package/src/media.ts +355 -0
- package/src/page-log.test.ts +163 -0
- package/src/page-log.ts +124 -0
- package/src/preview-compose.test.ts +637 -0
- package/src/preview-compose.ts +656 -0
- package/src/preview-scanner.test.ts +96 -0
- package/src/preview-scanner.ts +214 -0
- package/src/proposal-status.test.ts +69 -0
- package/src/proposal-status.ts +40 -0
- package/src/responsive-images.test.ts +104 -0
- package/src/responsive-images.ts +151 -0
- package/src/result.ts +29 -0
- package/src/safe-keys.ts +21 -0
- package/src/seo.test.ts +194 -0
- package/src/seo.ts +233 -0
- package/src/skills.ts +48 -0
- package/src/snapshots.test.ts +80 -0
- package/src/snapshots.ts +81 -0
- package/src/strip-cdata.test.ts +41 -0
- package/src/strip-cdata.ts +50 -0
- package/src/structured-sets.ts +114 -0
- package/src/subagents.test.ts +262 -0
- package/src/subagents.ts +432 -0
- package/src/template-engine.test.ts +379 -0
- package/src/template-engine.ts +520 -0
- package/src/theme-gradient.test.ts +92 -0
- package/src/theme-importers/__tests__/proto-pollution.test.ts +54 -0
- package/src/theme-importers/auto-detect.ts +84 -0
- package/src/theme-importers/css-comments.ts +15 -0
- package/src/theme-importers/dtcg.ts +106 -0
- package/src/theme-importers/loose.ts +76 -0
- package/src/theme-importers/shadcn.ts +133 -0
- package/src/theme-importers/style-dictionary.ts +125 -0
- package/src/theme-importers/tailwind.ts +217 -0
- package/src/theme-literal-binding.test.ts +71 -0
- package/src/theme-literal-binding.ts +159 -0
- package/src/theme-motion.test.ts +115 -0
- package/src/theme-normalize-envelope.test.ts +43 -0
- package/src/theme-normalize-gradient.test.ts +135 -0
- package/src/theme-normalize.ts +661 -0
- package/src/theme-ramp.ts +187 -0
- package/src/theme-render-sanitize.test.ts +45 -0
- package/src/theme-render.test.ts +119 -0
- package/src/theme-render.ts +487 -0
- package/src/theme-shadow.test.ts +56 -0
- package/src/themes-errors.ts +199 -0
- package/src/themes.ts +842 -0
- package/src/version.ts +66 -0
- package/dist/translation.d.ts +0 -127
- package/dist/translation.d.ts.map +0 -1
- package/dist/translation.js +0 -208
- package/dist/translation.js.map +0 -1
package/src/ai-tools.ts
ADDED
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schemas for the AI tools shipped in P5. Lives in @caelo-cms/shared so
|
|
5
|
+
* the provider abstraction (which streams tool-call args from the LLM)
|
|
6
|
+
* and the tool dispatcher (which validates + invokes the handler)
|
|
7
|
+
* import from a single source.
|
|
8
|
+
*
|
|
9
|
+
* `.strict()` on every input — the LLM occasionally hallucinates fields
|
|
10
|
+
* and we want a typed rejection at the Validator boundary, not silent
|
|
11
|
+
* silent drops in the handler.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
contentInstanceCreateSchema,
|
|
17
|
+
contentInstanceDeleteSchema,
|
|
18
|
+
contentInstanceUpdateSchema,
|
|
19
|
+
forkPlacementContentSchema,
|
|
20
|
+
MODULE_CSS_MAX,
|
|
21
|
+
MODULE_HTML_MAX,
|
|
22
|
+
MODULE_JS_MAX,
|
|
23
|
+
moduleFieldSchema,
|
|
24
|
+
setPlacementContentSchema,
|
|
25
|
+
slugSchema,
|
|
26
|
+
} from "./content.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* v0.6.2 — `position` argument shared across the three `add_module_to_*`
|
|
30
|
+
* tools. Accepts either the literal string `"top"` / `"bottom"` OR a
|
|
31
|
+
* 0..1000 integer index.
|
|
32
|
+
*
|
|
33
|
+
* The integer branch is `z.preprocess`-wrapped to coerce digit-only
|
|
34
|
+
* strings (`"0"`, `"42"`) to numbers before the int check. The AI
|
|
35
|
+
* recurrently passes JSON-quoted numbers (`"0"`) even though the tool
|
|
36
|
+
* description explicitly says to use bare integers; the coercion
|
|
37
|
+
* removes that ergonomic gotcha while still rejecting non-numeric
|
|
38
|
+
* strings (`"abc"` → still fails the int check cleanly).
|
|
39
|
+
*
|
|
40
|
+
* issue #106 (step-13 round-6) — an OUTER preprocess strips one layer of
|
|
41
|
+
* surrounding matching quotes first. The model occasionally over-quotes the
|
|
42
|
+
* literal: it emitted `position` as the JSON string `"\"bottom\""` (the
|
|
43
|
+
* 8-char value including the quote characters), which matched neither union
|
|
44
|
+
* branch and failed with a bare `Invalid input`. Unwrapping `"bottom"` →
|
|
45
|
+
* `bottom` (and `"\"0\""` → `0`, which the integer branch then coerces) turns
|
|
46
|
+
* that recurring first-call rejection into a clean accept. This is input
|
|
47
|
+
* NORMALISATION at the boundary (decoding a malformed encoding of a value the
|
|
48
|
+
* caller did supply), not a missing-data fallback — `undefined`/`null`/`""`
|
|
49
|
+
* still fall through to a loud rejection, per CLAUDE.md §2.
|
|
50
|
+
*/
|
|
51
|
+
function stripSurroundingQuotes(v: unknown): unknown {
|
|
52
|
+
if (typeof v !== "string") return v;
|
|
53
|
+
// Only when BOTH ends are the same quote char and there is at least one
|
|
54
|
+
// inner character — so a bare `"bottom"` / `"0"` is untouched and an empty
|
|
55
|
+
// `""` is not collapsed into a match here.
|
|
56
|
+
const m = v.match(/^(["'])([\s\S]+)\1$/);
|
|
57
|
+
return m ? m[2] : v;
|
|
58
|
+
}
|
|
59
|
+
export const positionInputSchema = z.preprocess(
|
|
60
|
+
stripSurroundingQuotes,
|
|
61
|
+
z.union([
|
|
62
|
+
z.enum(["top", "bottom"]),
|
|
63
|
+
z.preprocess(
|
|
64
|
+
(v) => (typeof v === "string" && /^\d+$/.test(v) ? Number.parseInt(v, 10) : v),
|
|
65
|
+
z.number().int().min(0).max(1000),
|
|
66
|
+
),
|
|
67
|
+
]),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const editModuleToolInput = z
|
|
71
|
+
.object({
|
|
72
|
+
moduleId: z.string().uuid(),
|
|
73
|
+
displayName: z.string().min(1).max(128).optional(),
|
|
74
|
+
/**
|
|
75
|
+
* v0.12.0 — rewrite the module's purpose. Optional; passing it
|
|
76
|
+
* updates the `## Modules` block your future self will read.
|
|
77
|
+
*/
|
|
78
|
+
description: z.string().max(1000).optional(),
|
|
79
|
+
/** v0.12.0 — re-classify the module's role tag. */
|
|
80
|
+
kind: z.enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
|
|
81
|
+
/**
|
|
82
|
+
* v0.12.3 (issue #106) — re-classify the module's stable `type` (the
|
|
83
|
+
* class a parent's `allowedModuleTypes` whitelist matches against,
|
|
84
|
+
* e.g. `button`). Rarely needed; usually derived from displayName at
|
|
85
|
+
* create time. Set it to make this module satisfy a parent's allowlist.
|
|
86
|
+
*/
|
|
87
|
+
type: slugSchema.optional(),
|
|
88
|
+
html: z.string().max(MODULE_HTML_MAX).optional(),
|
|
89
|
+
css: z.string().max(MODULE_CSS_MAX).optional(),
|
|
90
|
+
js: z.string().max(MODULE_JS_MAX).optional(),
|
|
91
|
+
/**
|
|
92
|
+
* v0.4.0 — module field schema. Each field declares one substitution
|
|
93
|
+
* slot referenced in the module HTML as `{{name}}`. Page placements
|
|
94
|
+
* fill these via `set_page_module_content`. Optional on edits — pass
|
|
95
|
+
* to replace the declared schema.
|
|
96
|
+
*/
|
|
97
|
+
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
98
|
+
/** issue #164 slice 2 — see addModuleToolInput.bindThemeLiterals. */
|
|
99
|
+
bindThemeLiterals: z.boolean().optional(),
|
|
100
|
+
})
|
|
101
|
+
.strict();
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* v0.4.0 — `set_page_module_content` AI tool. Fills the content values for
|
|
105
|
+
* a module placement on a specific page. Page-bound + branch-isolated per
|
|
106
|
+
* chat until publish (unlike `edit_module` which is global + immediate).
|
|
107
|
+
*/
|
|
108
|
+
export const setPageModuleContentToolInput = z
|
|
109
|
+
.object({
|
|
110
|
+
pageId: z.string().uuid(),
|
|
111
|
+
blockName: z.string().min(1).max(64),
|
|
112
|
+
position: z.number().int().nonnegative(),
|
|
113
|
+
/** Map of module field name → value. Fully replaces existing values. */
|
|
114
|
+
contentValues: z.record(z.string(), z.unknown()),
|
|
115
|
+
})
|
|
116
|
+
.strict();
|
|
117
|
+
export type SetPageModuleContentToolInput = z.infer<typeof setPageModuleContentToolInput>;
|
|
118
|
+
|
|
119
|
+
export const siteMemoryProposeToolInput = z
|
|
120
|
+
.object({
|
|
121
|
+
slot: z.enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
|
|
122
|
+
body: z.string().min(1).max(4000),
|
|
123
|
+
rationale: z.string().min(1).max(1000),
|
|
124
|
+
})
|
|
125
|
+
.strict();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Progressive-disclosure skill loading. The `## Skills` system-prompt block
|
|
129
|
+
* lists each active skill's slug + description; when a task matches one or
|
|
130
|
+
* more, the model calls `load_skill({slugs})` to pull their full instructions
|
|
131
|
+
* into the conversation (they persist as a tool result for the rest of the
|
|
132
|
+
* chat, so each skill loads at most once).
|
|
133
|
+
*
|
|
134
|
+
* Takes a LIST, not a single slug, per CLAUDE.md §11: a load is a routine
|
|
135
|
+
* operation and every routine operation ships in bulk form, with n=1 as its
|
|
136
|
+
* smallest case rather than a separate singular tool. Loading three skills was
|
|
137
|
+
* three provider round-trips whose only output was one tool call each.
|
|
138
|
+
*
|
|
139
|
+
* Capped at 5: skills are task-scoped, and a model that wants more than a
|
|
140
|
+
* handful at once has almost certainly matched the index too loosely — each
|
|
141
|
+
* body is a sizeable injection into history.
|
|
142
|
+
*/
|
|
143
|
+
export const loadSkillToolInput = z
|
|
144
|
+
.object({
|
|
145
|
+
slugs: z
|
|
146
|
+
.array(
|
|
147
|
+
z
|
|
148
|
+
.string()
|
|
149
|
+
.min(1)
|
|
150
|
+
.max(120)
|
|
151
|
+
.regex(/^[a-z0-9-]+$/, "lowercase letters/digits/hyphens"),
|
|
152
|
+
)
|
|
153
|
+
.min(1)
|
|
154
|
+
.max(5),
|
|
155
|
+
})
|
|
156
|
+
.strict();
|
|
157
|
+
export type LoadSkillToolInput = z.infer<typeof loadSkillToolInput>;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The set of tools shipped in P5. Other phases extend by adding a new
|
|
161
|
+
* entry; the dispatcher walks this map at registration time.
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* The ONE `add_module` input — consolidates add_module_to_{page,layout,template}
|
|
165
|
+
* (audit #2). `target` picks the destination; `targetRef` is a slug OR a uuid
|
|
166
|
+
* (resolved server-side — a slug is friendlier for the AI to hold than a uuid).
|
|
167
|
+
* Same two modes as before: `moduleId` reuses an existing module (now for ALL
|
|
168
|
+
* three targets — layout used to be mint-only), or the authoring fields mint a
|
|
169
|
+
* new one (raw html is fine — moduleize turns it into a proper module). Exactly
|
|
170
|
+
* one mode, enforced by the shared superRefine.
|
|
171
|
+
*/
|
|
172
|
+
export const addModuleToolInput = z
|
|
173
|
+
.object({
|
|
174
|
+
target: z.enum(["page", "layout", "template"]),
|
|
175
|
+
/** Slug or uuid of the page / layout / template. Resolved server-side. */
|
|
176
|
+
targetRef: z.string().min(1).max(200),
|
|
177
|
+
blockName: z.string().min(1).max(80),
|
|
178
|
+
position: positionInputSchema,
|
|
179
|
+
/** Reuse an existing module by id (any target). Mutually exclusive with the
|
|
180
|
+
* authoring fields. */
|
|
181
|
+
moduleId: z.string().uuid().optional(),
|
|
182
|
+
displayName: z.string().min(1).max(128).optional(),
|
|
183
|
+
description: z.string().max(1000).optional(),
|
|
184
|
+
kind: z.enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
|
|
185
|
+
type: slugSchema.optional(),
|
|
186
|
+
html: z.string().min(1).max(50_000).optional(),
|
|
187
|
+
css: z.string().max(50_000).optional(),
|
|
188
|
+
js: z.string().max(50_000).optional(),
|
|
189
|
+
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
190
|
+
bindThemeLiterals: z.boolean().optional(),
|
|
191
|
+
/**
|
|
192
|
+
* The placement's initial content, applied IN THE SAME CALL (2026-07
|
|
193
|
+
* — a freshly minted module must never land empty and wait for a
|
|
194
|
+
* second round-trip). ONE rule for every target: content goes in
|
|
195
|
+
* `values`. target='page': fills the placement's content_instance.
|
|
196
|
+
* target='template': every fanned-out placement gets these values.
|
|
197
|
+
* target='layout': chrome has no content_instance, so the handler
|
|
198
|
+
* stores the values as the minted module's `fields[].default` —
|
|
199
|
+
* which requires explicit `fields` (moduleize field names are
|
|
200
|
+
* unknowable up front) and is rejected for `moduleId` reuse (a
|
|
201
|
+
* shared module renders its stored defaults; edit_module changes
|
|
202
|
+
* them).
|
|
203
|
+
*/
|
|
204
|
+
values: z.record(z.string(), z.unknown()).optional(),
|
|
205
|
+
})
|
|
206
|
+
.strict()
|
|
207
|
+
.superRefine((input, ctx) => {
|
|
208
|
+
if (input.target === "layout" && input.values !== undefined) {
|
|
209
|
+
if (input.moduleId !== undefined) {
|
|
210
|
+
ctx.addIssue({
|
|
211
|
+
code: z.ZodIssueCode.custom,
|
|
212
|
+
message:
|
|
213
|
+
"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 edit_module.",
|
|
214
|
+
});
|
|
215
|
+
} else if (input.fields === undefined || input.fields.length === 0) {
|
|
216
|
+
ctx.addIssue({
|
|
217
|
+
code: z.ZodIssueCode.custom,
|
|
218
|
+
message:
|
|
219
|
+
"target='layout' with `values` needs explicit `fields` — chrome stores the values as field defaults, so the field names must be declared in this call (raw-HTML field inference can't know your value keys).",
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
const known = new Set(input.fields.map((f) => f.name));
|
|
223
|
+
const unknown = Object.keys(input.values).filter((k) => !known.has(k));
|
|
224
|
+
if (unknown.length > 0) {
|
|
225
|
+
ctx.addIssue({
|
|
226
|
+
code: z.ZodIssueCode.custom,
|
|
227
|
+
message: `\`values\` keys [${unknown.join(", ")}] match no declared field — declared: [${[...known].join(", ")}].`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// `moduleId` is PLACEMENT-ONLY (see the buildPageModuleSchema note): a
|
|
233
|
+
// placement that CAN succeed must not fail over an extra authoring field
|
|
234
|
+
// (§1A/§11), so we do NOT reject a moduleId call that also carries authoring
|
|
235
|
+
// fields. Those fields are not applied here; the tool HANDLER surfaces an
|
|
236
|
+
// INFO for any ignored field whose value DIFFERS from the module's stored
|
|
237
|
+
// one, pointing at edit_module — so a dropped change is never silent (§2).
|
|
238
|
+
if (input.moduleId !== undefined) return;
|
|
239
|
+
if (input.displayName === undefined || input.html === undefined) {
|
|
240
|
+
ctx.addIssue({
|
|
241
|
+
code: z.ZodIssueCode.custom,
|
|
242
|
+
message:
|
|
243
|
+
"Pass either `moduleId` (place an existing module from ## Modules) " +
|
|
244
|
+
"or `displayName` + `html` (mint a new module).",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// 2026-07 — a freshly minted module must carry its initial copy IN
|
|
248
|
+
// THIS call, via `values` on every target (`fields[].default` also
|
|
249
|
+
// satisfies it — that's where layout values land anyway). Without
|
|
250
|
+
// either, the placement rendered EMPTY until a second round-trip —
|
|
251
|
+
// the exact two-step build_page was created to kill. Only enforced
|
|
252
|
+
// when the caller authored explicit fields; the raw-HTML path goes
|
|
253
|
+
// through moduleize, which enforces defaults itself.
|
|
254
|
+
if (
|
|
255
|
+
input.fields !== undefined &&
|
|
256
|
+
input.fields.length > 0 &&
|
|
257
|
+
input.values === undefined &&
|
|
258
|
+
!input.fields.some((f) => "default" in f && f.default !== undefined)
|
|
259
|
+
) {
|
|
260
|
+
ctx.addIssue({
|
|
261
|
+
code: z.ZodIssueCode.custom,
|
|
262
|
+
message:
|
|
263
|
+
"A minted module needs its initial content IN THIS CALL: pass `values` " +
|
|
264
|
+
"({fieldName: value, …}) for this placement, or give the fields `default`s. " +
|
|
265
|
+
"Without either, the module renders empty placeholders until a second call.",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
export type AddModuleToolInput = z.infer<typeof addModuleToolInput>;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* v0.2.16 — `add_plugin_to_page` AI tool. Inserts a plugin's
|
|
273
|
+
* `<div data-caelo-plugin>` placeholder into a page's block via a
|
|
274
|
+
* synthetic module. The placeholder is what the static-generator's
|
|
275
|
+
* plugin pass (apps/static-generator/src/plugin-pass.ts) replaces
|
|
276
|
+
* with the plugin's `staticRender` output at deploy time, and what
|
|
277
|
+
* the plugin's Web Component hydrates against on the client. The
|
|
278
|
+
* tool resolves `plugin-host`'s in-memory registry to confirm the
|
|
279
|
+
* plugin is loaded + active before creating the module.
|
|
280
|
+
*/
|
|
281
|
+
export const addPluginToPageToolInput = z
|
|
282
|
+
.object({
|
|
283
|
+
pageId: z.string().uuid(),
|
|
284
|
+
pluginSlug: z.string().min(1).max(80),
|
|
285
|
+
blockName: z.string().min(1).max(80),
|
|
286
|
+
/** "top" | "bottom" | a 0-based integer index. */
|
|
287
|
+
position: positionInputSchema,
|
|
288
|
+
})
|
|
289
|
+
.strict();
|
|
290
|
+
|
|
291
|
+
// ─── v0.12.0 — content_instances + placement AI tools ─────────────────
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* `list_content_instances` — browse the content library. The `placementCount`
|
|
295
|
+
* returned per row is a blast-radius affordance: the AI can decide whether
|
|
296
|
+
* to edit a shared instance (propagates everywhere) or fork it first.
|
|
297
|
+
*/
|
|
298
|
+
export const listContentInstancesToolInput = z
|
|
299
|
+
.object({
|
|
300
|
+
moduleId: z.string().uuid().optional(),
|
|
301
|
+
slug: z.string().min(1).max(64).optional(),
|
|
302
|
+
/** Free-text matches against displayName + slug. Case-insensitive substring. */
|
|
303
|
+
search: z.string().min(1).max(128).optional(),
|
|
304
|
+
/** Narrow to "instances referenced from this page" for chat-runner planning. */
|
|
305
|
+
pageId: z.string().uuid().optional(),
|
|
306
|
+
})
|
|
307
|
+
.strict();
|
|
308
|
+
|
|
309
|
+
export const getContentInstanceToolInput = z
|
|
310
|
+
.object({
|
|
311
|
+
id: z.string().uuid(),
|
|
312
|
+
})
|
|
313
|
+
.strict();
|
|
314
|
+
|
|
315
|
+
export const createContentInstanceToolInput = contentInstanceCreateSchema;
|
|
316
|
+
export const setContentInstanceValuesToolInput = contentInstanceUpdateSchema;
|
|
317
|
+
export const deleteContentInstanceToolInput = contentInstanceDeleteSchema;
|
|
318
|
+
export const setPlacementContentToolInput = setPlacementContentSchema;
|
|
319
|
+
export const forkPlacementContentToolInput = forkPlacementContentSchema;
|
|
320
|
+
|
|
321
|
+
export const AI_TOOLS = [
|
|
322
|
+
"edit_module",
|
|
323
|
+
"site_memory_propose",
|
|
324
|
+
"add_module",
|
|
325
|
+
"create_page",
|
|
326
|
+
// audit #3 — page metadata (name/title/slug/template/status) is one tool for
|
|
327
|
+
// 1..200 pages: `update_pages_many`. The former rename_page / set_page_title
|
|
328
|
+
// / change_page_slug were single-field wrappers over the same pages.update.
|
|
329
|
+
"update_pages_many",
|
|
330
|
+
// audit #4 — one delete tool for 1..200 pages, disposition per page.
|
|
331
|
+
// The former singular delete_page folded in (n=1 is a one-item array).
|
|
332
|
+
"delete_pages_many",
|
|
333
|
+
// one remove-module tool routed by target (page|layout).
|
|
334
|
+
"remove_module_from",
|
|
335
|
+
"set_structured_set",
|
|
336
|
+
"update_theme",
|
|
337
|
+
"set_template_layout",
|
|
338
|
+
"create_layout",
|
|
339
|
+
"set_site_defaults",
|
|
340
|
+
"duplicate_page",
|
|
341
|
+
"repoint_page_template",
|
|
342
|
+
"move_module",
|
|
343
|
+
"reorder_module",
|
|
344
|
+
"set_nav_menu",
|
|
345
|
+
"add_plugin_to_page",
|
|
346
|
+
// v0.12.0 — content_instances + placement binding
|
|
347
|
+
"list_content_instances",
|
|
348
|
+
"get_content_instance",
|
|
349
|
+
"create_content_instance",
|
|
350
|
+
"set_content_instance_values",
|
|
351
|
+
"delete_content_instance",
|
|
352
|
+
"set_placement_content",
|
|
353
|
+
"fork_placement_content",
|
|
354
|
+
// issue #299 — bulk-first build path (§11)
|
|
355
|
+
"build_page",
|
|
356
|
+
"create_content_instances",
|
|
357
|
+
"set_page_module_content_many",
|
|
358
|
+
] as const;
|
|
359
|
+
export type AiToolName = (typeof AI_TOOLS)[number];
|
|
360
|
+
export type AddPluginToPageToolInput = z.infer<typeof addPluginToPageToolInput>;
|
|
361
|
+
export type ListContentInstancesToolInput = z.infer<typeof listContentInstancesToolInput>;
|
|
362
|
+
export type GetContentInstanceToolInput = z.infer<typeof getContentInstanceToolInput>;
|
|
363
|
+
export type CreateContentInstanceToolInput = z.infer<typeof createContentInstanceToolInput>;
|
|
364
|
+
export type SetContentInstanceValuesToolInput = z.infer<typeof setContentInstanceValuesToolInput>;
|
|
365
|
+
export type DeleteContentInstanceToolInput = z.infer<typeof deleteContentInstanceToolInput>;
|
|
366
|
+
export type SetPlacementContentToolInput = z.infer<typeof setPlacementContentToolInput>;
|
|
367
|
+
export type ForkPlacementContentToolInput = z.infer<typeof forkPlacementContentToolInput>;
|
|
368
|
+
|
|
369
|
+
/** Chat ops input shapes — used by the SvelteKit form actions. */
|
|
370
|
+
export const chatCreateSessionInput = z
|
|
371
|
+
.object({
|
|
372
|
+
title: z.string().min(1).max(200).optional(),
|
|
373
|
+
/** P6.7.4 — bind the new chat to one page (live-edit surface). */
|
|
374
|
+
pageId: z.string().uuid().optional(),
|
|
375
|
+
/** P6.7.4 — bind the new chat to one template (template editor). */
|
|
376
|
+
templateId: z.string().uuid().optional(),
|
|
377
|
+
/** P10.5 — when set, this is an ephemeral subagent session; sidebar filters it out. */
|
|
378
|
+
subagentRole: z.string().min(1).max(120).optional(),
|
|
379
|
+
/** P10.5 — parent chat session id for subagent attribution (audit trail). */
|
|
380
|
+
parentChatSessionId: z.string().uuid().nullable().optional(),
|
|
381
|
+
})
|
|
382
|
+
.strict();
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* issue #190 — an operator-attached image riding a user chat message.
|
|
386
|
+
* References a media_assets row (the upload endpoint owns validation
|
|
387
|
+
* + storage); image/* only in v1 so the provider mapping is always an
|
|
388
|
+
* image part.
|
|
389
|
+
*/
|
|
390
|
+
export const chatAttachmentSchema = z
|
|
391
|
+
.object({
|
|
392
|
+
/** Set for an operator upload — a `media_assets` row. */
|
|
393
|
+
assetId: z.string().uuid().optional(),
|
|
394
|
+
/**
|
|
395
|
+
* Set for an AI-produced chat image — an object-store key under
|
|
396
|
+
* `CHAT_IMAGE_PREFIX`. Deliberately not a media asset: see the prefix's
|
|
397
|
+
* doc comment. Exactly one of `assetId` / `storageKey` is present.
|
|
398
|
+
*/
|
|
399
|
+
storageKey: z.string().min(1).max(512).optional(),
|
|
400
|
+
mime: z.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]),
|
|
401
|
+
alt: z.string().max(2048).optional(),
|
|
402
|
+
})
|
|
403
|
+
.strict()
|
|
404
|
+
.refine((a) => (a.assetId === undefined) !== (a.storageKey === undefined), {
|
|
405
|
+
message: "exactly one of assetId (operator upload) or storageKey (chat image) must be set",
|
|
406
|
+
});
|
|
407
|
+
export type ChatAttachment = z.infer<typeof chatAttachmentSchema>;
|
|
408
|
+
|
|
409
|
+
/** issue #190 — attachments-per-message cap; bounds provider payload size. */
|
|
410
|
+
export const CHAT_MAX_ATTACHMENTS = 4;
|
|
411
|
+
|
|
412
|
+
export const chatSendMessageInput = z
|
|
413
|
+
.object({
|
|
414
|
+
chatSessionId: z.string().uuid(),
|
|
415
|
+
// Optional as of Plan B: a resume turn (see `resumeApproval`) carries no
|
|
416
|
+
// operator content — it re-runs the paused gated turn. A refine below
|
|
417
|
+
// requires exactly one of content / resumeApproval.
|
|
418
|
+
content: z.string().min(1).max(8000).optional(),
|
|
419
|
+
/** Element-reference chips appended to the message. */
|
|
420
|
+
chips: z
|
|
421
|
+
.array(
|
|
422
|
+
z
|
|
423
|
+
.object({
|
|
424
|
+
moduleId: z.string().uuid(),
|
|
425
|
+
selector: z.string().min(1).max(500),
|
|
426
|
+
label: z.string().min(1).max(200),
|
|
427
|
+
})
|
|
428
|
+
.strict(),
|
|
429
|
+
)
|
|
430
|
+
.default([]),
|
|
431
|
+
/** issue #190 — operator-attached images (see chatAttachmentSchema). */
|
|
432
|
+
attachments: z.array(chatAttachmentSchema).max(CHAT_MAX_ATTACHMENTS).default([]),
|
|
433
|
+
/**
|
|
434
|
+
* issue #29 — provenance of this turn. 'system' marks an auto-injected
|
|
435
|
+
* message the operator did NOT type (crawl-completion nudge, post-
|
|
436
|
+
* approval continuation): the model still receives it as a user turn,
|
|
437
|
+
* but the UI renders it as a muted status note instead of "You:".
|
|
438
|
+
* Omitted / undefined = operator-authored.
|
|
439
|
+
*/
|
|
440
|
+
origin: z.enum(["operator", "system"]).optional(),
|
|
441
|
+
/**
|
|
442
|
+
* P6.7.3 — the active /edit page id, threaded so the chat-runner can
|
|
443
|
+
* compose a Current-page volatile chunk in the system prompt and so
|
|
444
|
+
* tools that operate on a page (add_module target='page') know the target
|
|
445
|
+
* without a chip. Optional because the standalone chat editor at
|
|
446
|
+
* /content/chat doesn't have a page context.
|
|
447
|
+
*/
|
|
448
|
+
activePageId: z.string().uuid().optional(),
|
|
449
|
+
/**
|
|
450
|
+
* Plan B (SDK approval gate) — production resume of a paused gated turn.
|
|
451
|
+
* When set, this is NOT a new operator message: the runner appends the
|
|
452
|
+
* SDK tool-approval-response (the Owner's in-chat Approve/Reject) to the
|
|
453
|
+
* paused turn's history and re-runs so the SDK either executes the gated
|
|
454
|
+
* tool (approved) or lets the model react to the denial. `approvalId` is
|
|
455
|
+
* the SDK id surfaced on the tool-approval-request ClientEvent.
|
|
456
|
+
*/
|
|
457
|
+
resumeApproval: z
|
|
458
|
+
.object({
|
|
459
|
+
approvalId: z.string().min(1).max(200),
|
|
460
|
+
approved: z.boolean(),
|
|
461
|
+
reason: z.string().max(2000).optional(),
|
|
462
|
+
})
|
|
463
|
+
.strict()
|
|
464
|
+
.optional(),
|
|
465
|
+
})
|
|
466
|
+
.strict()
|
|
467
|
+
.refine((d) => d.content !== undefined || d.resumeApproval !== undefined, {
|
|
468
|
+
message: "content or resumeApproval is required",
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
export const chatRenameSessionInput = z
|
|
472
|
+
.object({
|
|
473
|
+
chatSessionId: z.string().uuid(),
|
|
474
|
+
title: z.string().min(1).max(200),
|
|
475
|
+
})
|
|
476
|
+
.strict();
|
|
477
|
+
|
|
478
|
+
export const chatPublishInput = z
|
|
479
|
+
.object({
|
|
480
|
+
chatSessionId: z.string().uuid(),
|
|
481
|
+
/**
|
|
482
|
+
* Optional partial-publish filter (P5.2 #5). When present, only
|
|
483
|
+
* entities listed here are merged into main; the rest stay on the
|
|
484
|
+
* branch for a later publish. Empty array means "publish nothing"
|
|
485
|
+
* and is rejected; omit the field to publish everything.
|
|
486
|
+
*/
|
|
487
|
+
entities: z
|
|
488
|
+
.array(
|
|
489
|
+
z
|
|
490
|
+
.object({
|
|
491
|
+
// v0.9.0 — added "layout" + "structuredSet" to the enum so
|
|
492
|
+
// the Stage picker can include/exclude them. Pre-v0.9.0 the
|
|
493
|
+
// helper accepted "structuredSet" via the underlying SQL
|
|
494
|
+
// query but the input schema rejected it — minor gap closed
|
|
495
|
+
// alongside the branched-create work.
|
|
496
|
+
kind: z.enum([
|
|
497
|
+
"module",
|
|
498
|
+
"template",
|
|
499
|
+
"page",
|
|
500
|
+
"pageLayout",
|
|
501
|
+
"pageModuleContent",
|
|
502
|
+
"layout",
|
|
503
|
+
"structuredSet",
|
|
504
|
+
// v0.11.0 (#45) — themes primitive joins the publish/Stage
|
|
505
|
+
// surface so chat-branched theme edits replay into live
|
|
506
|
+
// on chat.publish.
|
|
507
|
+
"theme",
|
|
508
|
+
]),
|
|
509
|
+
entityId: z.string().uuid(),
|
|
510
|
+
})
|
|
511
|
+
.strict(),
|
|
512
|
+
)
|
|
513
|
+
.min(1)
|
|
514
|
+
.optional(),
|
|
515
|
+
})
|
|
516
|
+
.strict();
|
|
517
|
+
|
|
518
|
+
export const aiMemorySetInput = z
|
|
519
|
+
.object({
|
|
520
|
+
slot: z.enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
|
|
521
|
+
body: z.string().max(4000),
|
|
522
|
+
})
|
|
523
|
+
.strict();
|
|
524
|
+
|
|
525
|
+
export const aiMemoryReviewInput = z
|
|
526
|
+
.object({
|
|
527
|
+
proposalId: z.string().uuid(),
|
|
528
|
+
decision: z.enum(["accept", "reject"]),
|
|
529
|
+
})
|
|
530
|
+
.strict();
|
|
531
|
+
|
|
532
|
+
export const aiProvidersSetInput = z
|
|
533
|
+
.object({
|
|
534
|
+
name: z.enum(["anthropic", "openai", "google", "local-openai-compat"]),
|
|
535
|
+
displayName: z.string().min(1).max(100),
|
|
536
|
+
config: z.record(z.string(), z.unknown()).default({}),
|
|
537
|
+
isActive: z.boolean().default(true),
|
|
538
|
+
/**
|
|
539
|
+
* Optional plaintext API key. When present the op encrypts it with the
|
|
540
|
+
* project KEK and persists ciphertext + IV + KEK fingerprint. When
|
|
541
|
+
* absent the existing stored key (if any) is preserved untouched —
|
|
542
|
+
* lets the Owner edit displayName / model / baseUrl without re-pasting.
|
|
543
|
+
* Audit logs `apiKeyChanged: boolean`, never the value.
|
|
544
|
+
*/
|
|
545
|
+
apiKey: z.string().min(1).max(500).optional(),
|
|
546
|
+
})
|
|
547
|
+
.strict();
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Input for `ai_providers.clear_key` — Owner-only NULLs the encrypted
|
|
551
|
+
* triplet so the resolver falls back to the env-var path for that
|
|
552
|
+
* provider (or returns null if no env is set, which surfaces the
|
|
553
|
+
* "configure AI provider" UI banner).
|
|
554
|
+
*/
|
|
555
|
+
export const aiProvidersClearKeyInput = z
|
|
556
|
+
.object({
|
|
557
|
+
name: z.enum(["anthropic", "openai", "google", "local-openai-compat"]),
|
|
558
|
+
})
|
|
559
|
+
.strict();
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* P6.7.5 — page-lifecycle tools. Three identifiers, three independent
|
|
563
|
+
* tools so the AI never silently substitutes one for another.
|
|
564
|
+
*/
|
|
565
|
+
const slugInputSchema = z
|
|
566
|
+
.string()
|
|
567
|
+
.min(1)
|
|
568
|
+
.max(120)
|
|
569
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters/digits/hyphens, leading non-hyphen");
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* P18 AI-completeness — `create_template` AI tool input. Wraps
|
|
573
|
+
* `templates.create` (widened to AI in this pass per CLAUDE.md §11
|
|
574
|
+
* default-AI-allowed scope). `layoutId` is optional; the op resolves to
|
|
575
|
+
* `site_defaults.default_layout_id` when omitted.
|
|
576
|
+
*/
|
|
577
|
+
export const createTemplateToolInput = z
|
|
578
|
+
.object({
|
|
579
|
+
slug: slugInputSchema,
|
|
580
|
+
displayName: z.string().min(1).max(256),
|
|
581
|
+
html: z.string().min(1).max(2_000_000),
|
|
582
|
+
css: z.string().max(2_000_000).default(""),
|
|
583
|
+
layoutId: z.string().uuid().optional(),
|
|
584
|
+
// Optional block-set metadata, symmetric with create_layout. Omit to
|
|
585
|
+
// auto-derive blocks from <caelo-slot> tags; pass to set displayName /
|
|
586
|
+
// position (each name must match a slot). Enforced op-side in
|
|
587
|
+
// templates.create (loud reject on a slot-less block).
|
|
588
|
+
blocks: z
|
|
589
|
+
.array(
|
|
590
|
+
z.object({
|
|
591
|
+
name: z.string().min(1).max(80),
|
|
592
|
+
displayName: z.string().min(1).max(200),
|
|
593
|
+
position: z.number().int().min(0).max(1000),
|
|
594
|
+
}),
|
|
595
|
+
)
|
|
596
|
+
.optional(),
|
|
597
|
+
})
|
|
598
|
+
.strict();
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* The ONE remove-module input — `target` picks page vs layout, `targetRef` is a
|
|
602
|
+
* slug OR a uuid (resolved server-side). Mirrors `add_module` so placement and
|
|
603
|
+
* un-placement share one shape. (No `template` target: there is no
|
|
604
|
+
* remove_module_from_template, matching the add-side history.)
|
|
605
|
+
*/
|
|
606
|
+
export const removeModuleFromToolInput = z
|
|
607
|
+
.object({
|
|
608
|
+
target: z.enum(["page", "layout"]),
|
|
609
|
+
targetRef: z.string().min(1).max(200),
|
|
610
|
+
moduleId: z.string().uuid(),
|
|
611
|
+
})
|
|
612
|
+
.strict();
|
|
613
|
+
export type RemoveModuleFromToolInput = z.infer<typeof removeModuleFromToolInput>;
|
|
614
|
+
|
|
615
|
+
export const setStructuredSetToolInput = z
|
|
616
|
+
.object({
|
|
617
|
+
// structuredSetKind (the 6th kind that was previously unreachable
|
|
618
|
+
// from any AI tool — the kind-specific wrappers covered only
|
|
619
|
+
// nav-menu + theme).
|
|
620
|
+
kind: z.enum(["nav-menu", "taxonomy", "tags", "link-list"]),
|
|
621
|
+
slug: slugInputSchema,
|
|
622
|
+
displayName: z.string().min(1).max(200),
|
|
623
|
+
items: z.array(z.unknown()),
|
|
624
|
+
})
|
|
625
|
+
.strict();
|
|
626
|
+
|
|
627
|
+
// v0.10.22 — `updateThemeToolInput` removed. The kind-specific `update_theme`
|
|
628
|
+
// wrapper was replaced by the unified `set_structured_set` surface.
|
|
629
|
+
// v0.11.0 (#45) — theme moved out of structured_sets entirely into its
|
|
630
|
+
// own `themes` primitive (DTCG-shaped jsonb). Theme edits are now
|
|
631
|
+
// `set_theme_tokens` (loose-name patch) / `propose_create_theme`
|
|
632
|
+
// (gated mint) / `propose_activate_theme` (gated flip) — see
|
|
633
|
+
// packages/admin-core/src/ai/tools/{update-theme-tokens,get-theme,…}.ts.
|
|
634
|
+
|
|
635
|
+
export const setTemplateLayoutToolInput = z
|
|
636
|
+
.object({
|
|
637
|
+
templateId: z.string().uuid(),
|
|
638
|
+
layoutSlug: slugInputSchema,
|
|
639
|
+
})
|
|
640
|
+
.strict();
|
|
641
|
+
|
|
642
|
+
export const createLayoutToolInput = z
|
|
643
|
+
.object({
|
|
644
|
+
slug: slugInputSchema,
|
|
645
|
+
displayName: z.string().min(1).max(200),
|
|
646
|
+
html: z.string().min(1).max(50_000),
|
|
647
|
+
css: z.string().max(50_000).optional(),
|
|
648
|
+
blocks: z
|
|
649
|
+
.array(
|
|
650
|
+
z
|
|
651
|
+
.object({
|
|
652
|
+
name: z.string().min(1).max(80),
|
|
653
|
+
displayName: z.string().min(1).max(200),
|
|
654
|
+
position: z.number().int().min(0).max(1000),
|
|
655
|
+
})
|
|
656
|
+
.strict(),
|
|
657
|
+
)
|
|
658
|
+
.min(1)
|
|
659
|
+
.max(20),
|
|
660
|
+
})
|
|
661
|
+
.strict();
|
|
662
|
+
|
|
663
|
+
export const setSiteDefaultsToolInput = z
|
|
664
|
+
.object({
|
|
665
|
+
defaultLayoutSlug: slugInputSchema.optional(),
|
|
666
|
+
defaultTemplateSlug: slugInputSchema.optional(),
|
|
667
|
+
})
|
|
668
|
+
.strict()
|
|
669
|
+
.refine((v) => v.defaultLayoutSlug !== undefined || v.defaultTemplateSlug !== undefined, {
|
|
670
|
+
message: "must provide at least one of defaultLayoutSlug, defaultTemplateSlug",
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* P6.7.7 — content-ops follow-ups: clone a page, swap a page's
|
|
675
|
+
* template, reorder modules within a block, move a module across
|
|
676
|
+
* blocks. All wrap existing or new ops; the tool layer captures the
|
|
677
|
+
* user-facing intent (which the system prompt steers the AI toward).
|
|
678
|
+
*/
|
|
679
|
+
export const duplicatePageToolInput = z
|
|
680
|
+
.object({
|
|
681
|
+
sourcePageId: z.string().uuid(),
|
|
682
|
+
newSlug: slugInputSchema,
|
|
683
|
+
newName: z.string().min(1).max(256).optional(),
|
|
684
|
+
newTitle: z.string().min(1).max(256).optional(),
|
|
685
|
+
targetTemplateId: z.string().uuid().optional(),
|
|
686
|
+
locale: z.string().min(2).max(10).optional(),
|
|
687
|
+
})
|
|
688
|
+
.strict();
|
|
689
|
+
|
|
690
|
+
export const repointPageTemplateToolInput = z
|
|
691
|
+
.object({
|
|
692
|
+
pageId: z.string().uuid(),
|
|
693
|
+
newTemplateId: z.string().uuid(),
|
|
694
|
+
/**
|
|
695
|
+
* `drop` discards modules in blocks that don't exist on the new
|
|
696
|
+
* template. `preserve-as-block` reroutes them to a named block on
|
|
697
|
+
* the new template (must exist). The AI should ASK the user when
|
|
698
|
+
* the diff would drop modules; only pass `drop` after explicit
|
|
699
|
+
* confirmation.
|
|
700
|
+
*/
|
|
701
|
+
orphanDisposition: z
|
|
702
|
+
.discriminatedUnion("kind", [
|
|
703
|
+
z.object({ kind: z.literal("drop") }).strict(),
|
|
704
|
+
z.object({ kind: z.literal("preserve-as-block"), blockName: slugInputSchema }).strict(),
|
|
705
|
+
])
|
|
706
|
+
.default({ kind: "drop" }),
|
|
707
|
+
})
|
|
708
|
+
.strict();
|
|
709
|
+
|
|
710
|
+
export const moveModuleToolInput = z
|
|
711
|
+
.object({
|
|
712
|
+
pageId: z.string().uuid(),
|
|
713
|
+
moduleId: z.string().uuid(),
|
|
714
|
+
toBlockName: z.string().min(1).max(80),
|
|
715
|
+
/** "top" | "bottom" | a 0-based index inside the destination block. */
|
|
716
|
+
position: positionInputSchema,
|
|
717
|
+
})
|
|
718
|
+
.strict();
|
|
719
|
+
|
|
720
|
+
export const reorderModuleToolInput = z
|
|
721
|
+
.object({
|
|
722
|
+
pageId: z.string().uuid(),
|
|
723
|
+
moduleId: z.string().uuid(),
|
|
724
|
+
/**
|
|
725
|
+
* `up` / `down` shift one slot. An integer is an absolute 0-based
|
|
726
|
+
* target position within the same block.
|
|
727
|
+
*/
|
|
728
|
+
direction: z.union([z.enum(["up", "down"]), z.number().int().min(0).max(1000)]),
|
|
729
|
+
})
|
|
730
|
+
.strict();
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* P7 — `find_media`. Searches the media library by alt-text /
|
|
734
|
+
* filename / mime. Returns up to `limit` matches with the WebP-800
|
|
735
|
+
* URL pre-resolved (or `orig` for non-image kinds). The system prompt
|
|
736
|
+
* already lists recent + frequently-used media; this tool covers the
|
|
737
|
+
* "search for an image of a sunlit office" case where the asset isn't
|
|
738
|
+
* in the recent slice.
|
|
739
|
+
*/
|
|
740
|
+
export const findMediaToolInput = z
|
|
741
|
+
.object({
|
|
742
|
+
query: z.string().max(256).optional(),
|
|
743
|
+
mime: z
|
|
744
|
+
.enum([
|
|
745
|
+
"image/jpeg",
|
|
746
|
+
"image/png",
|
|
747
|
+
"image/webp",
|
|
748
|
+
"image/avif",
|
|
749
|
+
"image/gif",
|
|
750
|
+
"image/svg+xml",
|
|
751
|
+
"application/pdf",
|
|
752
|
+
"video/mp4",
|
|
753
|
+
])
|
|
754
|
+
.optional(),
|
|
755
|
+
limit: z.number().int().min(1).max(50).default(15),
|
|
756
|
+
})
|
|
757
|
+
.strict();
|
|
758
|
+
export type FindMediaToolInput = z.infer<typeof findMediaToolInput>;
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* run #10 D4 — `regenerate_media_variants`. Recovery tool for the
|
|
762
|
+
* staging generator's "media references unresolved" failure: re-runs
|
|
763
|
+
* the image pipeline for the named assets (or every asset with an
|
|
764
|
+
* incomplete ladder) and reports per-asset what was added or why
|
|
765
|
+
* nothing could be.
|
|
766
|
+
*/
|
|
767
|
+
export const regenerateMediaVariantsToolInput = z
|
|
768
|
+
.object({
|
|
769
|
+
assetIds: z.array(z.string().uuid()).min(1).max(100).optional(),
|
|
770
|
+
allMissing: z.boolean().default(false),
|
|
771
|
+
})
|
|
772
|
+
.strict()
|
|
773
|
+
.refine((v) => (v.assetIds !== undefined) !== v.allMissing, {
|
|
774
|
+
message: "pass either assetIds or allMissing: true (exactly one)",
|
|
775
|
+
});
|
|
776
|
+
export type RegenerateMediaVariantsToolInput = z.infer<typeof regenerateMediaVariantsToolInput>;
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* P7 — `set_media_alt`. AI may improve a11y on an existing asset
|
|
780
|
+
* without a human round-trip (e.g. when the editor uploaded an image
|
|
781
|
+
* with the default alt and the AI reads its content). Updates only
|
|
782
|
+
* the alt field on `media_assets`.
|
|
783
|
+
*/
|
|
784
|
+
export const setMediaAltToolInput = z
|
|
785
|
+
.object({
|
|
786
|
+
assetId: z.string().uuid(),
|
|
787
|
+
alt: z.string().max(2048),
|
|
788
|
+
})
|
|
789
|
+
.strict();
|
|
790
|
+
export type SetMediaAltToolInput = z.infer<typeof setMediaAltToolInput>;
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* 0184 — `set_home_page`. AI designates a page as the site homepage (per
|
|
794
|
+
* locale, the locale root). Records `locales.home_page_id`; the page keeps
|
|
795
|
+
* its own slug but resolves to `/`.
|
|
796
|
+
*/
|
|
797
|
+
export const setHomePageToolInput = z
|
|
798
|
+
.object({
|
|
799
|
+
pageId: z.string().uuid(),
|
|
800
|
+
locale: z.string().min(2).max(10).optional(),
|
|
801
|
+
})
|
|
802
|
+
.strict();
|
|
803
|
+
export type SetHomePageToolInput = z.infer<typeof setHomePageToolInput>;
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* 0181 — `set_media_source`. AI records an asset's provenance (source
|
|
807
|
+
* kind + origin detail) and licence when known. All fields but the
|
|
808
|
+
* assetId are optional; the op COALESCEs so omitted fields stay
|
|
809
|
+
* unchanged.
|
|
810
|
+
*/
|
|
811
|
+
export const setMediaSourceToolInput = z
|
|
812
|
+
.object({
|
|
813
|
+
assetId: z.string().uuid(),
|
|
814
|
+
sourceKind: z.enum(["upload", "ai_generated", "imported", "external"]).optional(),
|
|
815
|
+
sourceDetail: z.string().max(2048).optional(),
|
|
816
|
+
license: z.string().max(200).optional(),
|
|
817
|
+
})
|
|
818
|
+
.strict();
|
|
819
|
+
export type SetMediaSourceToolInput = z.infer<typeof setMediaSourceToolInput>;
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* P8 — `set_page_seo`. Manual / panel writes to the per-page SEO
|
|
823
|
+
* sidecar. AI calls this only on explicit user intent
|
|
824
|
+
* ("set the home meta description to ..."). Doesn't bump fingerprints
|
|
825
|
+
* (autofilled_at / optimized_at).
|
|
826
|
+
*/
|
|
827
|
+
export const setPageSeoToolInput = z
|
|
828
|
+
.object({
|
|
829
|
+
pageId: z.string().uuid(),
|
|
830
|
+
metaDescription: z.string().max(320).optional(),
|
|
831
|
+
ogImageAssetId: z.string().uuid().nullable().optional(),
|
|
832
|
+
canonicalUrl: z.string().max(2048).nullable().optional(),
|
|
833
|
+
noindex: z.boolean().optional(),
|
|
834
|
+
changefreq: z
|
|
835
|
+
.enum(["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"])
|
|
836
|
+
.optional(),
|
|
837
|
+
priority: z.number().min(0).max(1).optional(),
|
|
838
|
+
})
|
|
839
|
+
.strict();
|
|
840
|
+
export type SetPageSeoToolInput = z.infer<typeof setPageSeoToolInput>;
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* P8 — `autofill_page_seo`. Fill-once. Refuses when the page's SEO
|
|
844
|
+
* was already autofilled. Triggered by the `seo-autofill` skill on
|
|
845
|
+
* the first-publish path.
|
|
846
|
+
*/
|
|
847
|
+
export const autofillPageSeoToolInput = z
|
|
848
|
+
.object({
|
|
849
|
+
pageId: z.string().uuid(),
|
|
850
|
+
metaDescription: z.string().min(1).max(320),
|
|
851
|
+
ogImageAssetId: z.string().uuid().nullable().optional(),
|
|
852
|
+
})
|
|
853
|
+
.strict();
|
|
854
|
+
export type AutofillPageSeoToolInput = z.infer<typeof autofillPageSeoToolInput>;
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* P8 — `optimize_page_seo`. Explicit re-optimization. Always allowed.
|
|
858
|
+
* Takes optional context (keyword analysis / intent shifts / branding
|
|
859
|
+
* changes). The AI can call this across N pages in one chat turn —
|
|
860
|
+
* the resulting changes batch into one Publish-pill confirm.
|
|
861
|
+
*/
|
|
862
|
+
export const optimizePageSeoToolInput = z
|
|
863
|
+
.object({
|
|
864
|
+
pageId: z.string().uuid(),
|
|
865
|
+
metaDescription: z.string().min(1).max(320),
|
|
866
|
+
ogImageAssetId: z.string().uuid().nullable().optional(),
|
|
867
|
+
context: z.string().max(4000).optional(),
|
|
868
|
+
})
|
|
869
|
+
.strict();
|
|
870
|
+
export type OptimizePageSeoToolInput = z.infer<typeof optimizePageSeoToolInput>;
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* P8 AI-first review pass — bulk variants. Per CLAUDE.md §11, bulk
|
|
874
|
+
* tools save round-trips when the AI knows it has N changes to make
|
|
875
|
+
* in a single turn. The handlers run inside one transaction so the
|
|
876
|
+
* batch is all-or-nothing.
|
|
877
|
+
*/
|
|
878
|
+
export const findRedirectsToolInput = z
|
|
879
|
+
.object({
|
|
880
|
+
query: z.string().max(500).optional(),
|
|
881
|
+
statusCode: z
|
|
882
|
+
.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308), z.literal(410)])
|
|
883
|
+
.optional(),
|
|
884
|
+
limit: z.number().int().min(1).max(200).default(50),
|
|
885
|
+
})
|
|
886
|
+
.strict();
|
|
887
|
+
export type FindRedirectsToolInput = z.infer<typeof findRedirectsToolInput>;
|
|
888
|
+
|
|
889
|
+
export const bulkCreateRedirectsToolInput = z
|
|
890
|
+
.object({
|
|
891
|
+
redirects: z
|
|
892
|
+
.array(
|
|
893
|
+
z
|
|
894
|
+
.object({
|
|
895
|
+
fromPath: z.string().min(1).max(500),
|
|
896
|
+
toPath: z.string().min(1).max(500),
|
|
897
|
+
statusCode: z
|
|
898
|
+
.union([
|
|
899
|
+
z.literal(301),
|
|
900
|
+
z.literal(302),
|
|
901
|
+
z.literal(307),
|
|
902
|
+
z.literal(308),
|
|
903
|
+
z.literal(410),
|
|
904
|
+
])
|
|
905
|
+
.default(301),
|
|
906
|
+
})
|
|
907
|
+
.strict(),
|
|
908
|
+
)
|
|
909
|
+
.min(1)
|
|
910
|
+
.max(500),
|
|
911
|
+
upsert: z.boolean().default(false),
|
|
912
|
+
})
|
|
913
|
+
.strict();
|
|
914
|
+
export type BulkCreateRedirectsToolInput = z.infer<typeof bulkCreateRedirectsToolInput>;
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* v0.9.13 — Singular + bulk status-flip AI tool inputs.
|
|
918
|
+
*
|
|
919
|
+
* Wraps the `pages.set_status` and `pages.set_status_many` ops. Drafts
|
|
920
|
+
* are LIVE-EDIT ONLY; Stage and Production deploys filter to
|
|
921
|
+
* `status='published'`. Use the bulk form for "publish all drafts" or
|
|
922
|
+
* any N>1 flip — saves N round-trips + N snapshot writes + N audit rows.
|
|
923
|
+
*/
|
|
924
|
+
export const setPagesStatusManyToolInput = z
|
|
925
|
+
.object({
|
|
926
|
+
pageIds: z.array(z.string().uuid()).min(1).max(200),
|
|
927
|
+
status: z.enum(["draft", "published"]),
|
|
928
|
+
})
|
|
929
|
+
.strict();
|
|
930
|
+
export type SetPagesStatusManyToolInput = z.infer<typeof setPagesStatusManyToolInput>;
|
|
931
|
+
|
|
932
|
+
export const bulkDeleteRedirectsToolInput = z
|
|
933
|
+
.object({
|
|
934
|
+
redirectIds: z.array(z.string().uuid()).max(500).optional(),
|
|
935
|
+
fromPaths: z.array(z.string().min(1).max(500)).max(500).optional(),
|
|
936
|
+
matches: z.string().min(1).max(500).optional(),
|
|
937
|
+
})
|
|
938
|
+
.strict();
|
|
939
|
+
export type BulkDeleteRedirectsToolInput = z.infer<typeof bulkDeleteRedirectsToolInput>;
|
|
940
|
+
|
|
941
|
+
export const bulkOptimizeSeoToolInput = z
|
|
942
|
+
.object({
|
|
943
|
+
updates: z
|
|
944
|
+
.array(
|
|
945
|
+
z
|
|
946
|
+
.object({
|
|
947
|
+
pageId: z.string().uuid(),
|
|
948
|
+
metaDescription: z.string().min(1).max(320),
|
|
949
|
+
ogImageAssetId: z.string().uuid().nullable().optional(),
|
|
950
|
+
})
|
|
951
|
+
.strict(),
|
|
952
|
+
)
|
|
953
|
+
.min(1)
|
|
954
|
+
.max(200),
|
|
955
|
+
context: z.string().max(4000).optional(),
|
|
956
|
+
})
|
|
957
|
+
.strict();
|
|
958
|
+
export type BulkOptimizeSeoToolInput = z.infer<typeof bulkOptimizeSeoToolInput>;
|
|
959
|
+
|
|
960
|
+
export type EditModuleToolInput = z.infer<typeof editModuleToolInput>;
|
|
961
|
+
export type SiteMemoryProposeToolInput = z.infer<typeof siteMemoryProposeToolInput>;
|
|
962
|
+
export type CreateTemplateToolInput = z.infer<typeof createTemplateToolInput>;
|
|
963
|
+
export type SetStructuredSetToolInput = z.infer<typeof setStructuredSetToolInput>;
|
|
964
|
+
// v0.10.22 — `UpdateThemeToolInput` removed alongside `update_theme` tool.
|
|
965
|
+
export type ChatCreateSessionInput = z.infer<typeof chatCreateSessionInput>;
|
|
966
|
+
export type ChatSendMessageInput = z.infer<typeof chatSendMessageInput>;
|
|
967
|
+
export type ChatRenameSessionInput = z.infer<typeof chatRenameSessionInput>;
|
|
968
|
+
export type ChatPublishInput = z.infer<typeof chatPublishInput>;
|
|
969
|
+
export type AiMemorySetInput = z.infer<typeof aiMemorySetInput>;
|
|
970
|
+
export type AiMemoryReviewInput = z.infer<typeof aiMemoryReviewInput>;
|
|
971
|
+
export type AiProvidersSetInput = z.infer<typeof aiProvidersSetInput>;
|
|
972
|
+
export type AiProvidersClearKeyInput = z.infer<typeof aiProvidersClearKeyInput>;
|
|
973
|
+
export type SetTemplateLayoutToolInput = z.infer<typeof setTemplateLayoutToolInput>;
|
|
974
|
+
export type CreateLayoutToolInput = z.infer<typeof createLayoutToolInput>;
|
|
975
|
+
export type SetSiteDefaultsToolInput = z.infer<typeof setSiteDefaultsToolInput>;
|
|
976
|
+
export type DuplicatePageToolInput = z.infer<typeof duplicatePageToolInput>;
|
|
977
|
+
export type RepointPageTemplateToolInput = z.infer<typeof repointPageTemplateToolInput>;
|
|
978
|
+
export type MoveModuleToolInput = z.infer<typeof moveModuleToolInput>;
|
|
979
|
+
export type ReorderModuleToolInput = z.infer<typeof reorderModuleToolInput>;
|
|
980
|
+
// v0.10.22 — `SetNavMenuToolInput` removed alongside `set_nav_menu` tool.
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* P10A — `propose_skill`. AI drafts a new skill body (or revision) and
|
|
984
|
+
* queues it for Owner review. Per CLAUDE.md §2: skills augment the AI's
|
|
985
|
+
* own system prompt, so site-wide activation requires explicit Owner
|
|
986
|
+
* confirmation. The proposal lands in skill_proposals; Owner accepts
|
|
987
|
+
* (creates `skills` row at status='awaiting_activation') and then
|
|
988
|
+
* activates separately.
|
|
989
|
+
*/
|
|
990
|
+
export const proposeSkillToolInput = z
|
|
991
|
+
.object({
|
|
992
|
+
slug: z
|
|
993
|
+
.string()
|
|
994
|
+
.min(1)
|
|
995
|
+
.max(120)
|
|
996
|
+
.regex(/^[a-z0-9-]+$/, "lowercase letters/digits/hyphens"),
|
|
997
|
+
displayName: z.string().min(1).max(200),
|
|
998
|
+
description: z.string().max(1000).default(""),
|
|
999
|
+
body: z.string().min(1).max(20000),
|
|
1000
|
+
rationale: z.string().min(1).max(1000),
|
|
1001
|
+
allowlistedTools: z.array(z.string().min(1).max(120)).default([]),
|
|
1002
|
+
hints: z
|
|
1003
|
+
.object({
|
|
1004
|
+
keywords: z.array(z.string().min(1).max(80)).default([]),
|
|
1005
|
+
chipTrigger: z.boolean().default(false),
|
|
1006
|
+
alwaysOn: z.boolean().default(false),
|
|
1007
|
+
})
|
|
1008
|
+
.strict()
|
|
1009
|
+
.default({ keywords: [], chipTrigger: false, alwaysOn: false }),
|
|
1010
|
+
})
|
|
1011
|
+
.strict();
|
|
1012
|
+
export type ProposeSkillToolInput = z.infer<typeof proposeSkillToolInput>;
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* P11 — `submit_plugin`. AI submits a Tier 2 plugin for validation +
|
|
1016
|
+
* Owner approval. CLAUDE.md §2 invariant: AI submits, human Owner
|
|
1017
|
+
* activates. Tier 1 plugins ship via human PR + signed release; the AI
|
|
1018
|
+
* tool surface cannot promote — the manifest field `tier` is forced
|
|
1019
|
+
* to 2 by the handler.
|
|
1020
|
+
*/
|
|
1021
|
+
export const submitPluginToolInput = z
|
|
1022
|
+
.object({
|
|
1023
|
+
slug: z
|
|
1024
|
+
.string()
|
|
1025
|
+
.min(1)
|
|
1026
|
+
.max(120)
|
|
1027
|
+
.regex(/^[a-z][a-z0-9-]*$/, "lowercase, dash-separated"),
|
|
1028
|
+
version: z
|
|
1029
|
+
.string()
|
|
1030
|
+
.min(1)
|
|
1031
|
+
.max(40)
|
|
1032
|
+
.regex(/^\d+\.\d+\.\d+(-[a-z0-9.]+)?$/, "semver"),
|
|
1033
|
+
/** Manifest object as written by the plugin author. Tier-1 fields
|
|
1034
|
+
* (`requestedCapabilities`, `workers`, `tools`) and `tier: 1`
|
|
1035
|
+
* are rejected by the validator. */
|
|
1036
|
+
manifest: z.record(z.string(), z.unknown()),
|
|
1037
|
+
/** Full source code of the plugin's compiled JS module. */
|
|
1038
|
+
source: z.string().min(1).max(200_000),
|
|
1039
|
+
})
|
|
1040
|
+
.strict();
|
|
1041
|
+
export type SubmitPluginToolInput = z.infer<typeof submitPluginToolInput>;
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* v0.6.0 W4 — composite workflow tool. Bootstraps a fresh install in
|
|
1045
|
+
* one tool call: creates a default layout (header/content/footer), a
|
|
1046
|
+
* default template (single `content` block), and pins both via
|
|
1047
|
+
* `site_defaults.set`. Replaces the 4-5 step bootstrap dance the AI
|
|
1048
|
+
* currently has to orchestrate via the bootstrap-site skill.
|
|
1049
|
+
*
|
|
1050
|
+
* Every field is optional + has a sensible default — the AI can call
|
|
1051
|
+
* with `{}` on the smallest case and get a working scaffold.
|
|
1052
|
+
*/
|
|
1053
|
+
export const bootstrapSiteScaffoldToolInput = z
|
|
1054
|
+
.object({
|
|
1055
|
+
/** Slug for the new layout. Defaults to `site-default`. */
|
|
1056
|
+
layoutSlug: z.string().min(1).max(120).optional(),
|
|
1057
|
+
/** Display name for the new layout. Defaults to `Site default`. */
|
|
1058
|
+
layoutDisplayName: z.string().min(1).max(256).optional(),
|
|
1059
|
+
/** Block names for the layout. Defaults to header/content/footer.
|
|
1060
|
+
* The `content` block is REQUIRED (where the template renders);
|
|
1061
|
+
* the validator inserts it if missing. */
|
|
1062
|
+
layoutBlocks: z
|
|
1063
|
+
.array(
|
|
1064
|
+
z.object({
|
|
1065
|
+
name: z
|
|
1066
|
+
.string()
|
|
1067
|
+
.min(1)
|
|
1068
|
+
.max(64)
|
|
1069
|
+
.regex(/^[a-z][a-z0-9-]*$/),
|
|
1070
|
+
displayName: z.string().min(1).max(128),
|
|
1071
|
+
}),
|
|
1072
|
+
)
|
|
1073
|
+
.max(8)
|
|
1074
|
+
.optional(),
|
|
1075
|
+
/** Slug for the new template. Defaults to `home`. */
|
|
1076
|
+
templateSlug: z.string().min(1).max(120).optional(),
|
|
1077
|
+
/** Display name for the new template. Defaults to `Home template`. */
|
|
1078
|
+
templateDisplayName: z.string().min(1).max(256).optional(),
|
|
1079
|
+
/** Whether to pin the new layout+template as site_defaults. Defaults to true. */
|
|
1080
|
+
setAsDefaults: z.boolean().optional(),
|
|
1081
|
+
})
|
|
1082
|
+
.strict();
|
|
1083
|
+
export type BootstrapSiteScaffoldToolInput = z.infer<typeof bootstrapSiteScaffoldToolInput>;
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* v0.6.0 W4 (deferred) — composite revert. Undoes every snapshot tagged
|
|
1087
|
+
* with the given chat's branch_id. Per-entity revert ops handle the
|
|
1088
|
+
* fan-out; this composite groups them so a single AI tool call wipes
|
|
1089
|
+
* everything in a chat instead of N tool calls per touched entity.
|
|
1090
|
+
*
|
|
1091
|
+
* Bounded: when the chat touched more than `maxEntities` entities, the
|
|
1092
|
+
* tool refuses and asks the operator to use the per-entity revert UI;
|
|
1093
|
+
* keeps the AI from accidentally reverting a wide-ranging chat in one
|
|
1094
|
+
* click.
|
|
1095
|
+
*/
|
|
1096
|
+
export const revertChatChangesToolInput = z
|
|
1097
|
+
.object({
|
|
1098
|
+
chatSessionId: z.string().uuid(),
|
|
1099
|
+
/** Safety cap — when the chat's branch touched more than this many
|
|
1100
|
+
* entities, the composite refuses. Default 20. Set higher
|
|
1101
|
+
* explicitly when the user really wants a wide revert. */
|
|
1102
|
+
maxEntities: z.number().int().min(1).max(500).optional(),
|
|
1103
|
+
})
|
|
1104
|
+
.strict();
|
|
1105
|
+
export type RevertChatChangesToolInput = z.infer<typeof revertChatChangesToolInput>;
|