@elytracms/studio 0.0.57 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/components/ai/ai-chat-panel.js +44 -6
  2. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  3. package/dist/components/ai/chat-ui.d.ts +1 -1
  4. package/dist/components/ai/chat-ui.js +11 -2
  5. package/dist/components/ai/chat-ui.js.map +1 -1
  6. package/dist/components/ai/proposal-card.d.ts +30 -7
  7. package/dist/components/ai/proposal-card.js +81 -21
  8. package/dist/components/ai/proposal-card.js.map +1 -1
  9. package/dist/components/cms/detail-editor.js +12 -0
  10. package/dist/components/cms/detail-editor.js.map +1 -1
  11. package/dist/components/cms/document-diff-view.d.ts +12 -1
  12. package/dist/components/cms/document-diff-view.js +119 -42
  13. package/dist/components/cms/document-diff-view.js.map +1 -1
  14. package/dist/components/cms/rich-text-preview.d.ts +15 -0
  15. package/dist/components/cms/rich-text-preview.js +81 -0
  16. package/dist/components/cms/rich-text-preview.js.map +1 -0
  17. package/dist/components/cms/version-compare-text.d.ts +26 -0
  18. package/dist/components/cms/version-compare-text.js +60 -0
  19. package/dist/components/cms/version-compare-text.js.map +1 -1
  20. package/dist/lib/ai/apply-content.js +52 -10
  21. package/dist/lib/ai/apply-content.js.map +1 -1
  22. package/dist/lib/ai/chat-context.d.ts +19 -3
  23. package/dist/lib/ai/chat-context.js +33 -4
  24. package/dist/lib/ai/chat-context.js.map +1 -1
  25. package/dist/lib/ai/chat-state.d.ts +6 -3
  26. package/dist/lib/ai/chat-state.js.map +1 -1
  27. package/dist/lib/ai/fake-provider.d.ts +60 -5
  28. package/dist/lib/ai/fake-provider.js +74 -6
  29. package/dist/lib/ai/fake-provider.js.map +1 -1
  30. package/dist/lib/ai/index.d.ts +2 -0
  31. package/dist/lib/ai/index.js +2 -0
  32. package/dist/lib/ai/index.js.map +1 -1
  33. package/dist/lib/ai/rich-text-input.d.ts +27 -0
  34. package/dist/lib/ai/rich-text-input.js +25 -0
  35. package/dist/lib/ai/rich-text-input.js.map +1 -0
  36. package/dist/lib/ai/shape.d.ts +12 -1
  37. package/dist/lib/ai/shape.js +46 -20
  38. package/dist/lib/ai/shape.js.map +1 -1
  39. package/dist/lib/ai/tools.d.ts +88 -13
  40. package/dist/lib/ai/tools.js +253 -14
  41. package/dist/lib/ai/tools.js.map +1 -1
  42. package/dist/lib/ai/types.d.ts +20 -0
  43. package/dist/lib/ai/types.js.map +1 -1
  44. package/dist/lib/ai/update-fields.d.ts +68 -0
  45. package/dist/lib/ai/update-fields.js +296 -0
  46. package/dist/lib/ai/update-fields.js.map +1 -0
  47. package/dist/lib/ai-current-document.d.ts +53 -0
  48. package/dist/lib/ai-current-document.js +54 -0
  49. package/dist/lib/ai-current-document.js.map +1 -0
  50. package/dist/lib/cms-views/view-model.d.ts +6 -6
  51. package/dist/lib/i18n/messages.en.js +10 -1
  52. package/dist/lib/i18n/messages.en.js.map +1 -1
  53. package/package.json +2 -2
@@ -10,30 +10,33 @@
10
10
  * - relation fields become an ENUM over the provided candidate ids (or are omitted
11
11
  * when no candidates exist) — so a hallucinated relation id is not representable
12
12
  * as valid structured output.
13
- * - rich-text fields are emitted in the `{ version, doc }` ProseMirror storage shape
14
- * (the `allow` leash on embeds is enforced later by `validateRichTextEmbeds`).
13
+ * - asset/image fields become an ENUM over the searched library asset ids (or are
14
+ * omitted when none were searched) — EC-381, the SAME leash as relations: a
15
+ * proposed asset id must be a real, `listAssets`-returned library asset, and a
16
+ * create shape with no searched assets simply has no asset property (empty beats
17
+ * wrong; the assistant never generates or uploads).
18
+ * - rich-text fields are MARKDOWN STRINGS (EC-379): the description names the
19
+ * allowed subset, and the engine converts md → the `{ version, doc }` storage
20
+ * envelope at proposal-build time. The field's `allow` config leashes component
21
+ * EMBEDS only — markdown cannot express an embed, so the leash is satisfied by
22
+ * construction (and still enforced downstream by `validateRichTextEmbeds`).
15
23
  *
16
24
  * Localized fields stay flat in the shape (the model authors one locale at a time);
17
25
  * the engine routes their values under `localized[locale]`.
18
26
  */
19
27
  import { collectionJsonSchema, resolveLabel } from '@elytracms/core/cms-core';
20
- /** The rich-text `{ version, doc }` storage envelope, as a JSON Schema. */
21
- function richTextEnvelopeSchema() {
28
+ import { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text';
29
+ /**
30
+ * A rich-text field's model-facing schema (EC-379): a MARKDOWN string. The
31
+ * description carries the allowed subset (from the engine converter — one source
32
+ * of truth), so the model has something concrete to aim at; the engine converts
33
+ * the markdown into the `{ version, doc }` storage envelope at proposal-build
34
+ * time and rejects out-of-subset constructs with explicit issues.
35
+ */
36
+ function richTextMarkdownSchema() {
22
37
  return {
23
- type: 'object',
24
- description: 'Rich text in the ProseMirror storage shape: { version, doc: { type: "doc", content: [...] } }.',
25
- properties: {
26
- version: { type: 'number' },
27
- doc: {
28
- type: 'object',
29
- properties: {
30
- type: { type: 'string', const: 'doc' },
31
- content: { type: 'array', items: { type: 'object' } },
32
- },
33
- required: ['type', 'content'],
34
- },
35
- },
36
- required: ['version', 'doc'],
38
+ type: 'string',
39
+ description: `Rich text, written as ${MARKDOWN_PROSE_DESCRIPTION} Write genuine, publishable prose.`,
37
40
  };
38
41
  }
39
42
  /** A field's model-facing description: resolved label + optional `ai.hint`. */
@@ -56,6 +59,7 @@ export function buildTargetShape(input) {
56
59
  const excluded = [];
57
60
  const localizedFields = [];
58
61
  const relationFields = [];
62
+ const assetFields = [];
59
63
  for (const field of input.collection.fields) {
60
64
  if (field.localized)
61
65
  localizedFields.push(field.name);
@@ -89,8 +93,30 @@ export function buildTargetShape(input) {
89
93
  candidateIds: ids,
90
94
  });
91
95
  }
96
+ else if (field.type === 'asset') {
97
+ // EC-381 — same leash as relations: an asset field is an enum over the ids
98
+ // the model actually searched in the media library (`listAssets`). No
99
+ // searched assets ⇒ omit the field, so the proposal simply leaves it empty
100
+ // (an invented / to-be-uploaded id is structurally unrepresentable).
101
+ const candidates = input.assetCandidates?.[field.name] ?? [];
102
+ if (candidates.length === 0) {
103
+ delete properties[field.name];
104
+ continue;
105
+ }
106
+ const ids = candidates.map((c) => c.id);
107
+ const label = candidates
108
+ .map((c) => (c.label ? `${c.id} (${c.label})` : c.id))
109
+ .join(', ');
110
+ const idSchema = {
111
+ type: 'string',
112
+ enum: ids,
113
+ description: `Choose an EXISTING library asset id, one of: ${label}. Leave empty if none fits.`,
114
+ };
115
+ properties[field.name] = field.multiple ? { type: 'array', items: idSchema } : idSchema;
116
+ assetFields.push({ fieldName: field.name, multiple: field.multiple, candidateIds: ids });
117
+ }
92
118
  else if (field.type === 'richText') {
93
- properties[field.name] = richTextEnvelopeSchema();
119
+ properties[field.name] = richTextMarkdownSchema();
94
120
  }
95
121
  const prop = properties[field.name];
96
122
  if (prop) {
@@ -108,6 +134,6 @@ export function buildTargetShape(input) {
108
134
  additionalProperties: false,
109
135
  ...(required.length > 0 ? { required } : {}),
110
136
  };
111
- return { schema, excluded, localizedFields, relationFields };
137
+ return { schema, excluded, localizedFields, relationFields, assetFields };
112
138
  }
113
139
  //# sourceMappingURL=shape.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../src/lib/ai/shape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Target-shape derivation for `applyContentWithShape` (EC-331).\n *\n * Reuses the existing collection→JSON-Schema derivation (`collectionJsonSchema`,\n * cms-core) rather than writing a new one, then applies the three leashes the AI\n * engine adds on top:\n *\n * - `ai.exclude` fields are removed entirely (the model never sees them → cannot\n * write them).\n * - relation fields become an ENUM over the provided candidate ids (or are omitted\n * when no candidates exist) — so a hallucinated relation id is not representable\n * as valid structured output.\n * - rich-text fields are emitted in the `{ version, doc }` ProseMirror storage shape\n * (the `allow` leash on embeds is enforced later by `validateRichTextEmbeds`).\n *\n * Localized fields stay flat in the shape (the model authors one locale at a time);\n * the engine routes their values under `localized[locale]`.\n */\nimport { collectionJsonSchema, resolveLabel } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport type { RelationCandidates } from './types'\n\n/** A relation field's resolved candidate set — how the engine maps ids → reference envelopes. */\nexport interface TargetRelationField {\n fieldName: string\n /** The relation's target collection id. */\n target: string\n multiple: boolean\n /** The allowed candidate ids (the enum the model chose from). */\n candidateIds: string[]\n}\n\n/** The derived target shape + the metadata the engine needs to post-process the output. */\nexport interface TargetShape {\n /** The JSON Schema the structured output must satisfy. */\n schema: JsonSchema\n /** Field names removed by `ai.exclude` (untouchable). */\n excluded: string[]\n /** Field names whose values route under `localized[locale]`. */\n localizedFields: string[]\n /** Relation fields with their candidate enums. */\n relationFields: TargetRelationField[]\n}\n\nexport interface BuildTargetShapeInput {\n collection: CollectionDef\n /** Relation candidate ids by field — a relation field with none is omitted from the shape. */\n candidates?: RelationCandidates\n /** UI locale for resolving field labels into descriptions. */\n locale?: string\n}\n\n/** The rich-text `{ version, doc }` storage envelope, as a JSON Schema. */\nfunction richTextEnvelopeSchema(): JsonSchema {\n return {\n type: 'object',\n description:\n 'Rich text in the ProseMirror storage shape: { version, doc: { type: \"doc\", content: [...] } }.',\n properties: {\n version: { type: 'number' },\n doc: {\n type: 'object',\n properties: {\n type: { type: 'string', const: 'doc' },\n content: { type: 'array', items: { type: 'object' } },\n },\n required: ['type', 'content'],\n },\n },\n required: ['version', 'doc'],\n }\n}\n\n/** A field's model-facing description: resolved label + optional `ai.hint`. */\nfunction describe(field: FieldDef, locale?: string): string | undefined {\n const label = resolveLabel(field.form?.label, locale) ?? field.name\n const hint = field.ai?.hint\n const parts = [label, hint].filter((p): p is string => typeof p === 'string' && p.length > 0)\n return parts.length > 0 ? parts.join(' — ') : undefined\n}\n\n/**\n * Build the leash-aware target shape from a collection + candidates. Deterministic;\n * never throws. Reuses `collectionJsonSchema` as the base, then rewrites relations /\n * rich text and strips excluded fields.\n */\nexport function buildTargetShape(input: BuildTargetShapeInput): TargetShape {\n const base = collectionJsonSchema(input.collection)\n const properties: Record<string, JsonSchema> = {\n ...((base.properties as Record<string, JsonSchema>) ?? {}),\n }\n const excluded: string[] = []\n const localizedFields: string[] = []\n const relationFields: TargetRelationField[] = []\n\n for (const field of input.collection.fields) {\n if (field.localized) localizedFields.push(field.name)\n\n if (field.ai?.exclude) {\n excluded.push(field.name)\n delete properties[field.name]\n continue\n }\n\n if (field.type === 'relation') {\n const candidates = input.candidates?.[field.name] ?? []\n if (candidates.length === 0) {\n // No candidate ids ⇒ omit the field: the model cannot emit a relation value\n // at all, so a hallucinated id is structurally impossible.\n delete properties[field.name]\n continue\n }\n const ids = candidates.map((c) => c.id)\n const label = candidates\n .map((c) => (c.label ? `${c.id} (${c.label})` : c.id))\n .join(', ')\n const idSchema: JsonSchema = {\n type: 'string',\n enum: ids,\n description: `Choose one of: ${label}`,\n }\n properties[field.name] = field.multiple ? { type: 'array', items: idSchema } : idSchema\n relationFields.push({\n fieldName: field.name,\n target: field.target,\n multiple: field.multiple,\n candidateIds: ids,\n })\n } else if (field.type === 'richText') {\n properties[field.name] = richTextEnvelopeSchema()\n }\n\n const prop = properties[field.name]\n if (prop) {\n const desc = describe(field, input.locale)\n // Preserve an existing description (e.g. relation enum / rich-text note).\n if (desc && typeof prop.description !== 'string') prop.description = desc\n }\n }\n\n const requiredIn = Array.isArray(base.required) ? (base.required as string[]) : []\n const required = requiredIn.filter((n) => !excluded.includes(n) && properties[n] !== undefined)\n\n const schema: JsonSchema = {\n type: 'object',\n properties,\n additionalProperties: false,\n ...(required.length > 0 ? { required } : {}),\n }\n return { schema, excluded, localizedFields, relationFields }\n}\n"]}
1
+ {"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../src/lib/ai/shape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Target-shape derivation for `applyContentWithShape` (EC-331).\n *\n * Reuses the existing collection→JSON-Schema derivation (`collectionJsonSchema`,\n * cms-core) rather than writing a new one, then applies the three leashes the AI\n * engine adds on top:\n *\n * - `ai.exclude` fields are removed entirely (the model never sees them → cannot\n * write them).\n * - relation fields become an ENUM over the provided candidate ids (or are omitted\n * when no candidates exist) — so a hallucinated relation id is not representable\n * as valid structured output.\n * - asset/image fields become an ENUM over the searched library asset ids (or are\n * omitted when none were searched) — EC-381, the SAME leash as relations: a\n * proposed asset id must be a real, `listAssets`-returned library asset, and a\n * create shape with no searched assets simply has no asset property (empty beats\n * wrong; the assistant never generates or uploads).\n * - rich-text fields are MARKDOWN STRINGS (EC-379): the description names the\n * allowed subset, and the engine converts md → the `{ version, doc }` storage\n * envelope at proposal-build time. The field's `allow` config leashes component\n * EMBEDS only — markdown cannot express an embed, so the leash is satisfied by\n * construction (and still enforced downstream by `validateRichTextEmbeds`).\n *\n * Localized fields stay flat in the shape (the model authors one locale at a time);\n * the engine routes their values under `localized[locale]`.\n */\nimport { collectionJsonSchema, resolveLabel } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef, JsonSchema } from '@elytracms/core/cms-core'\nimport { MARKDOWN_PROSE_DESCRIPTION } from '@elytracms/core/rich-text'\nimport type { AssetCandidates, RelationCandidates } from './types'\n\n/** A relation field's resolved candidate set — how the engine maps ids → reference envelopes. */\nexport interface TargetRelationField {\n fieldName: string\n /** The relation's target collection id. */\n target: string\n multiple: boolean\n /** The allowed candidate ids (the enum the model chose from). */\n candidateIds: string[]\n}\n\n/** An asset field's resolved candidate set — how the engine maps ids → asset envelopes (EC-381). */\nexport interface TargetAssetField {\n fieldName: string\n multiple: boolean\n /** The allowed library asset ids (the enum the model chose from). */\n candidateIds: string[]\n}\n\n/** The derived target shape + the metadata the engine needs to post-process the output. */\nexport interface TargetShape {\n /** The JSON Schema the structured output must satisfy. */\n schema: JsonSchema\n /** Field names removed by `ai.exclude` (untouchable). */\n excluded: string[]\n /** Field names whose values route under `localized[locale]`. */\n localizedFields: string[]\n /** Relation fields with their candidate enums. */\n relationFields: TargetRelationField[]\n /** Asset fields with their candidate enums (EC-381). */\n assetFields: TargetAssetField[]\n}\n\nexport interface BuildTargetShapeInput {\n collection: CollectionDef\n /** Relation candidate ids by field — a relation field with none is omitted from the shape. */\n candidates?: RelationCandidates\n /** Asset candidate ids by field — an asset field with none is omitted from the shape (EC-381). */\n assetCandidates?: AssetCandidates\n /** UI locale for resolving field labels into descriptions. */\n locale?: string\n}\n\n/**\n * A rich-text field's model-facing schema (EC-379): a MARKDOWN string. The\n * description carries the allowed subset (from the engine converter — one source\n * of truth), so the model has something concrete to aim at; the engine converts\n * the markdown into the `{ version, doc }` storage envelope at proposal-build\n * time and rejects out-of-subset constructs with explicit issues.\n */\nfunction richTextMarkdownSchema(): JsonSchema {\n return {\n type: 'string',\n description: `Rich text, written as ${MARKDOWN_PROSE_DESCRIPTION} Write genuine, publishable prose.`,\n }\n}\n\n/** A field's model-facing description: resolved label + optional `ai.hint`. */\nfunction describe(field: FieldDef, locale?: string): string | undefined {\n const label = resolveLabel(field.form?.label, locale) ?? field.name\n const hint = field.ai?.hint\n const parts = [label, hint].filter((p): p is string => typeof p === 'string' && p.length > 0)\n return parts.length > 0 ? parts.join(' — ') : undefined\n}\n\n/**\n * Build the leash-aware target shape from a collection + candidates. Deterministic;\n * never throws. Reuses `collectionJsonSchema` as the base, then rewrites relations /\n * rich text and strips excluded fields.\n */\nexport function buildTargetShape(input: BuildTargetShapeInput): TargetShape {\n const base = collectionJsonSchema(input.collection)\n const properties: Record<string, JsonSchema> = {\n ...((base.properties as Record<string, JsonSchema>) ?? {}),\n }\n const excluded: string[] = []\n const localizedFields: string[] = []\n const relationFields: TargetRelationField[] = []\n const assetFields: TargetAssetField[] = []\n\n for (const field of input.collection.fields) {\n if (field.localized) localizedFields.push(field.name)\n\n if (field.ai?.exclude) {\n excluded.push(field.name)\n delete properties[field.name]\n continue\n }\n\n if (field.type === 'relation') {\n const candidates = input.candidates?.[field.name] ?? []\n if (candidates.length === 0) {\n // No candidate ids ⇒ omit the field: the model cannot emit a relation value\n // at all, so a hallucinated id is structurally impossible.\n delete properties[field.name]\n continue\n }\n const ids = candidates.map((c) => c.id)\n const label = candidates\n .map((c) => (c.label ? `${c.id} (${c.label})` : c.id))\n .join(', ')\n const idSchema: JsonSchema = {\n type: 'string',\n enum: ids,\n description: `Choose one of: ${label}`,\n }\n properties[field.name] = field.multiple ? { type: 'array', items: idSchema } : idSchema\n relationFields.push({\n fieldName: field.name,\n target: field.target,\n multiple: field.multiple,\n candidateIds: ids,\n })\n } else if (field.type === 'asset') {\n // EC-381 — same leash as relations: an asset field is an enum over the ids\n // the model actually searched in the media library (`listAssets`). No\n // searched assets ⇒ omit the field, so the proposal simply leaves it empty\n // (an invented / to-be-uploaded id is structurally unrepresentable).\n const candidates = input.assetCandidates?.[field.name] ?? []\n if (candidates.length === 0) {\n delete properties[field.name]\n continue\n }\n const ids = candidates.map((c) => c.id)\n const label = candidates\n .map((c) => (c.label ? `${c.id} (${c.label})` : c.id))\n .join(', ')\n const idSchema: JsonSchema = {\n type: 'string',\n enum: ids,\n description: `Choose an EXISTING library asset id, one of: ${label}. Leave empty if none fits.`,\n }\n properties[field.name] = field.multiple ? { type: 'array', items: idSchema } : idSchema\n assetFields.push({ fieldName: field.name, multiple: field.multiple, candidateIds: ids })\n } else if (field.type === 'richText') {\n properties[field.name] = richTextMarkdownSchema()\n }\n\n const prop = properties[field.name]\n if (prop) {\n const desc = describe(field, input.locale)\n // Preserve an existing description (e.g. relation enum / rich-text note).\n if (desc && typeof prop.description !== 'string') prop.description = desc\n }\n }\n\n const requiredIn = Array.isArray(base.required) ? (base.required as string[]) : []\n const required = requiredIn.filter((n) => !excluded.includes(n) && properties[n] !== undefined)\n\n const schema: JsonSchema = {\n type: 'object',\n properties,\n additionalProperties: false,\n ...(required.length > 0 ? { required } : {}),\n }\n return { schema, excluded, localizedFields, relationFields, assetFields }\n}\n"]}
@@ -14,13 +14,19 @@
14
14
  * a write path; apply is a separate, human-gated draft write.
15
15
  * 3. {@link createChatToolExecutor} — the client executor map `useChat`'s
16
16
  * `onToolCall` dispatches into. Readers pass through; the terminal tools run
17
- * the deterministic engines (`applyContentWithShape` / `translateDocument`)
18
- * through the route's kept `generate` branch and return a COMPACT proposal
19
- * summary as the tool output — the full proposal goes to the review UI via
17
+ * the deterministic engines (`applyContentWithShape` / `translateDocument` /
18
+ * `buildUpdateProposal`) and return a COMPACT proposal summary as the tool
19
+ * output — the full proposal goes to the review UI via
20
20
  * `onProposal(toolCallId, …)`, never to the model.
21
21
  *
22
- * EC-378 adds `updateFields` as one more definition + one more executor case —
23
- * the map's shape leaves that slot open.
22
+ * EC-378: `updateFields` is the edit-flow terminal tool — the model authors
23
+ * per-field changes directly (it has `getDocument` output + the conversation as
24
+ * context); the executor reads the CURRENT document, builds a validated update
25
+ * proposal (`buildUpdateProposal`), and hands it to the review sink. Its target
26
+ * (and `getDocument`'s) may be omitted to mean the document currently open in
27
+ * the editor — resolved via the {@link ChatToolExecutorOptions.currentDocument}
28
+ * seam ({@link resolveDocumentTarget}); the context block the panel injects is
29
+ * ADVISORY, the resolution here is what makes "this document" deterministic.
24
30
  *
25
31
  * The EC-199 planner enum/schema (`plannerSchema` / `parsePlannerAction`) retired
26
32
  * with the hand-rolled orchestrator (EC-334 slice 2).
@@ -29,7 +35,7 @@ import { z } from 'zod';
29
35
  import type { CmsDocument, CollectionDef, CollectionRegistry, LocaleConfig, ValidateDocumentOptions } from '@elytracms/core/cms-core';
30
36
  import type { ComponentManifest } from '@elytracms/core/component-registry';
31
37
  import type { ChatProposal } from './chat-state.js';
32
- import type { AiProvider, RelationCandidates, ResolvedKnowledge } from './types.js';
38
+ import type { AiProvider, AssetCandidates, RelationCandidates, ResolvedKnowledge } from './types.js';
33
39
  /** A document the model may reference as a relation target (from a `searchDocuments` read). */
34
40
  export interface DocumentSearchResult {
35
41
  id: string;
@@ -108,8 +114,8 @@ export declare const chatToolDefinitions: {
108
114
  getDocument: {
109
115
  description: string;
110
116
  inputSchema: z.ZodObject<{
111
- collection: z.ZodString;
112
- id: z.ZodString;
117
+ collection: z.ZodOptional<z.ZodString>;
118
+ id: z.ZodOptional<z.ZodString>;
113
119
  }, z.core.$strip>;
114
120
  };
115
121
  createDocument: {
@@ -119,6 +125,20 @@ export declare const chatToolDefinitions: {
119
125
  instruction: z.ZodString;
120
126
  }, z.core.$strip>;
121
127
  };
128
+ updateFields: {
129
+ description: string;
130
+ inputSchema: z.ZodObject<{
131
+ collection: z.ZodOptional<z.ZodString>;
132
+ id: z.ZodOptional<z.ZodString>;
133
+ changes: z.ZodArray<z.ZodObject<{
134
+ field: z.ZodString;
135
+ locale: z.ZodOptional<z.ZodString>;
136
+ blockId: z.ZodOptional<z.ZodString>;
137
+ prop: z.ZodOptional<z.ZodString>;
138
+ value: z.ZodOptional<z.ZodUnknown>;
139
+ }, z.core.$strip>>;
140
+ }, z.core.$strip>;
141
+ };
122
142
  translateDocument: {
123
143
  description: string;
124
144
  inputSchema: z.ZodObject<{
@@ -134,6 +154,33 @@ export interface ChatToolCall {
134
154
  toolName: string;
135
155
  input: unknown;
136
156
  }
157
+ /** The coordinates of the document currently open in the editor (EC-378 seam). */
158
+ export interface CurrentDocumentRef {
159
+ collection: string;
160
+ id: string;
161
+ }
162
+ /**
163
+ * Resolve a tool call's target document from its (optional) input coordinates
164
+ * and the current-document seam (EC-378). Deterministic:
165
+ *
166
+ * - both given → use them verbatim;
167
+ * - both omitted → the currently open document (error when none is open);
168
+ * - `collection` given with `id` omitted → the open document ONLY when the
169
+ * collections match — a named collection must never silently cross onto a
170
+ * different open document.
171
+ *
172
+ * Pure — tested directly; `getDocument` and `updateFields` share it.
173
+ */
174
+ export declare function resolveDocumentTarget(input: {
175
+ collection?: string;
176
+ id?: string;
177
+ }, current: CurrentDocumentRef | null | undefined): {
178
+ ok: true;
179
+ ref: CurrentDocumentRef;
180
+ } | {
181
+ ok: false;
182
+ errorText: string;
183
+ };
137
184
  /**
138
185
  * A tool execution outcome: an `output` posted back as the tool result, or an
139
186
  * `errorText` posted as an `output-error` part (the model sees the failure and
@@ -147,16 +194,27 @@ export type ChatToolOutcome = {
147
194
  errorText: string;
148
195
  };
149
196
  /**
150
- * The per-conversation relation-candidate store: every `searchDocuments` result
151
- * is recorded per collection, and `createDocument` may only reference the ids
152
- * gathered here (the `applyContentWithShape` enum leash makes a hallucinated id
153
- * structurally unrepresentable). Held by the panel per conversation — a new
154
- * conversation (EC-380 clear) starts an empty store.
197
+ * The per-conversation candidate store: every `searchDocuments` result is recorded
198
+ * per relation target collection, every `listAssets` result into one flat library
199
+ * pool (EC-381), and the terminal tools may only reference the ids gathered here
200
+ * (the `applyContentWithShape` / `buildUpdateProposal` enum + drop/loud leash makes
201
+ * a hallucinated relation OR asset id structurally unrepresentable / fail loud).
202
+ * Held by the panel per conversation — a new conversation (EC-380 clear) starts an
203
+ * empty store.
155
204
  */
156
205
  export interface ChatCandidateStore {
157
206
  record(collection: string, results: readonly DocumentSearchResult[]): void;
207
+ /** Record every `listAssets` result into the flat library pool (EC-381). */
208
+ recordAssets(results: readonly AssetSearchResult[]): void;
158
209
  /** Candidates for a target collection's relation fields, keyed by field name. */
159
210
  candidatesFor(collection: CollectionDef): RelationCandidates;
211
+ /**
212
+ * Candidates for a target collection's asset/image fields (EC-381), keyed by
213
+ * field name — every asset field shares the one flat searched-asset pool
214
+ * (`listAssets` has no field dimension). Empty when nothing was searched, so the
215
+ * asset field is then omitted from the shape (empty beats wrong).
216
+ */
217
+ assetCandidatesFor(collection: CollectionDef): AssetCandidates;
160
218
  }
161
219
  export declare function createChatCandidateStore(): ChatCandidateStore;
162
220
  export interface ChatToolExecutorOptions {
@@ -176,6 +234,13 @@ export interface ChatToolExecutorOptions {
176
234
  * The tool OUTPUT stays a compact summary; the patch never reaches the model.
177
235
  */
178
236
  onProposal: (toolCallId: string, proposal: ChatProposal) => void;
237
+ /**
238
+ * The document currently open in the editor (EC-378 seam) — a thunk read at
239
+ * EXECUTION time, so "this document" resolves against what is open NOW.
240
+ * `getDocument` / `updateFields` fall back to it when the model omits the
241
+ * target (see {@link resolveDocumentTarget}).
242
+ */
243
+ currentDocument?: () => CurrentDocumentRef | null | undefined;
179
244
  /** Current knowledge snapshot (thunk — the body thunk refreshes it per request). */
180
245
  knowledge?: () => ResolvedKnowledge | undefined;
181
246
  /** Component manifests — context for `allow`-leashed richText/blocks fields. */
@@ -197,6 +262,16 @@ export interface ChatToolExecutorOptions {
197
262
  export interface ChatToolExecutor {
198
263
  execute(call: ChatToolCall): Promise<ChatToolOutcome>;
199
264
  }
265
+ /**
266
+ * The MODEL-facing view of a document (EC-379): rich-text field values are
267
+ * projected to markdown strings — but ONLY when the engine's loss-guarded
268
+ * inverse can express them faithfully (subset-only content, round-trip proven).
269
+ * A document carrying embeds, internal `ref` links, or out-of-subset marks keeps
270
+ * its `{ version, doc }` envelope, so nothing the model later echoes back can
271
+ * silently lose structure. Applies to the `getDocument` TOOL OUTPUT only; the
272
+ * proposal/apply paths always read the raw stored document.
273
+ */
274
+ export declare function projectRichTextAsMarkdown(document: CmsDocument, collection: CollectionDef): CmsDocument;
200
275
  /**
201
276
  * Build the client executor map. Every entry validates its input against
202
277
  * {@link chatToolDefinitions}' zod schema (the route validates UIMessages only