@case-framework/survey-assistant 0.8.0 → 0.9.0
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/{authoring-references-DbuOKerg.mjs → authoring-references-CmwLc_C8.mjs} +40 -32
- package/dist/authoring-references-CmwLc_C8.mjs.map +1 -0
- package/dist/capabilities-D55dq-fO.mjs +582 -0
- package/dist/capabilities-D55dq-fO.mjs.map +1 -0
- package/dist/capabilities-default.d.mts +4 -12
- package/dist/capabilities-default.d.mts.map +1 -1
- package/dist/capabilities-default.mjs +2 -2
- package/dist/capabilities-z95e1mti.d.mts +117 -0
- package/dist/capabilities-z95e1mti.d.mts.map +1 -0
- package/dist/{constants-B6HzpEsx.d.mts → constants-HL_klgJl.d.mts} +4 -4
- package/dist/{constants-B6HzpEsx.d.mts.map → constants-HL_klgJl.d.mts.map} +1 -1
- package/dist/{controller-proxy-DEeFl9IA.mjs → controller-proxy-BQ1YsVxJ.mjs} +4 -3
- package/dist/controller-proxy-BQ1YsVxJ.mjs.map +1 -0
- package/dist/{controller-proxy-D7uQfm5f.d.mts → controller-proxy-Cmzdr9tV.d.mts} +4 -4
- package/dist/{controller-proxy-D7uQfm5f.d.mts.map → controller-proxy-Cmzdr9tV.d.mts.map} +1 -1
- package/dist/default-D22AKTqE.mjs +233 -0
- package/dist/default-D22AKTqE.mjs.map +1 -0
- package/dist/{digest-C39WyAWG.d.mts → digest-FnTTDBq2.d.mts} +2 -2
- package/dist/digest-FnTTDBq2.d.mts.map +1 -0
- package/dist/digest.d.mts +1 -1
- package/dist/engine-D950TfvC.mjs +3098 -0
- package/dist/engine-D950TfvC.mjs.map +1 -0
- package/dist/engine.d.mts +4 -3
- package/dist/engine.mjs +3 -3
- package/dist/{index-WB5kfnz0.d.mts → index-B8TLBd1G.d.mts} +8 -6
- package/dist/index-B8TLBd1G.d.mts.map +1 -0
- package/dist/index-CaX3hZmr.d.mts +23813 -0
- package/dist/index-CaX3hZmr.d.mts.map +1 -0
- package/dist/{index-r5riv4md.d.mts → index-CeUlGkoU.d.mts} +27 -6
- package/dist/index-CeUlGkoU.d.mts.map +1 -0
- package/dist/{index-D7wT6P3t.d.mts → index-DYHdvSE8.d.mts} +83 -41
- package/dist/index-DYHdvSE8.d.mts.map +1 -0
- package/dist/lifecycle-DFzxVxpq.d.mts +54346 -0
- package/dist/lifecycle-DFzxVxpq.d.mts.map +1 -0
- package/dist/{memory-thread-repository-DAnOGIB3.mjs → memory-thread-repository-DmLadygY.mjs} +15 -9
- package/dist/{memory-thread-repository-DAnOGIB3.mjs.map → memory-thread-repository-DmLadygY.mjs.map} +1 -1
- package/dist/protocol-BQ7uIDYT.mjs +569 -0
- package/dist/protocol-BQ7uIDYT.mjs.map +1 -0
- package/dist/protocol.d.mts +4 -4
- package/dist/protocol.mjs +3 -2
- package/dist/{react-C_GY-Fsc.mjs → react-DybJ5pWw.mjs} +17 -11
- package/dist/react-DybJ5pWw.mjs.map +1 -0
- package/dist/react-integration.d.mts +1 -1
- package/dist/react-integration.mjs +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.mjs +2 -2
- package/dist/references/assistant-operations.md +190 -215
- package/dist/references/core-rules.md +2 -2
- package/dist/references/element-types.md +189 -0
- package/dist/references/expressions.md +215 -238
- package/dist/references/follow-ups.md +151 -0
- package/dist/references/localization.md +26 -12
- package/dist/references/response-variables.md +3 -3
- package/dist/references/rich-text-content.md +22 -43
- package/dist/references/source-material-surveys.md +20 -25
- package/dist/references/survey-data-model.md +36 -117
- package/dist/{request-context-Dg4jSwN1.mjs → request-context-KVh8VjgH.mjs} +4 -4
- package/dist/request-context-KVh8VjgH.mjs.map +1 -0
- package/dist/server-agent.d.mts +28 -7
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +33 -25
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +6 -5
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +3 -3
- package/dist/server-tasks.d.mts +1 -1
- package/dist/server-tasks.mjs +1 -1
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +7 -6
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +3 -3
- package/dist/{tasks-BZEiwgxA.mjs → tasks-Coh5N027.mjs} +16 -15
- package/dist/tasks-Coh5N027.mjs.map +1 -0
- package/dist/{tasks-B9h3LPam.d.mts → tasks-wb9M-bak.d.mts} +11 -11
- package/dist/{tasks-B9h3LPam.d.mts.map → tasks-wb9M-bak.d.mts.map} +1 -1
- package/dist/{thread-handlers-Bef0td9N.d.mts → thread-handlers-C3Iya92v.d.mts} +6 -5
- package/dist/thread-handlers-C3Iya92v.d.mts.map +1 -0
- package/dist/{tools-CEsv-WoO.mjs → tools-D0KBDolR.mjs} +435 -408
- package/dist/tools-D0KBDolR.mjs.map +1 -0
- package/dist/turn-survey-draft-B34lggSG.d.mts +185 -0
- package/dist/turn-survey-draft-B34lggSG.d.mts.map +1 -0
- package/dist/ui.d.mts +3 -3
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +35 -23
- package/dist/ui.mjs.map +1 -1
- package/package.json +7 -7
- package/dist/authoring-references-DbuOKerg.mjs.map +0 -1
- package/dist/capabilities-CmeeAjEc.d.mts +0 -192
- package/dist/capabilities-CmeeAjEc.d.mts.map +0 -1
- package/dist/capabilities-DCMA71PP.mjs +0 -58
- package/dist/capabilities-DCMA71PP.mjs.map +0 -1
- package/dist/controller-proxy-DEeFl9IA.mjs.map +0 -1
- package/dist/default-fhSon2RM.mjs +0 -760
- package/dist/default-fhSon2RM.mjs.map +0 -1
- package/dist/digest-C39WyAWG.d.mts.map +0 -1
- package/dist/engine-nXoCWYpQ.mjs +0 -6888
- package/dist/engine-nXoCWYpQ.mjs.map +0 -1
- package/dist/index-Ciq7vHIs.d.mts +0 -716
- package/dist/index-Ciq7vHIs.d.mts.map +0 -1
- package/dist/index-D7wT6P3t.d.mts.map +0 -1
- package/dist/index-WB5kfnz0.d.mts.map +0 -1
- package/dist/index-r5riv4md.d.mts.map +0 -1
- package/dist/lifecycle-Dm8u7QFh.d.mts +0 -545
- package/dist/lifecycle-Dm8u7QFh.d.mts.map +0 -1
- package/dist/protocol-CF4Bh_Ch.mjs +0 -1255
- package/dist/protocol-CF4Bh_Ch.mjs.map +0 -1
- package/dist/react-C_GY-Fsc.mjs.map +0 -1
- package/dist/references/embedded-forms.md +0 -146
- package/dist/references/item-types.md +0 -641
- package/dist/request-context-Dg4jSwN1.mjs.map +0 -1
- package/dist/tasks-BZEiwgxA.mjs.map +0 -1
- package/dist/thread-handlers-Bef0td9N.d.mts.map +0 -1
- package/dist/tools-CEsv-WoO.mjs.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { f as surveyChangeSetSchema } from "./protocol-BQ7uIDYT.mjs";
|
|
2
|
+
import { E as surveyOperationSchemasByKind, T as surveyOperationSchema, a as SURVEY_OPERATION_KINDS, r as assistantElementTypeManifestSchema } from "./capabilities-D55dq-fO.mjs";
|
|
3
|
+
import { stableStringify } from "./digest.mjs";
|
|
4
|
+
import { A as itemSummarySchema, B as summarizeItemIndexEntries, D as getRawSurveyItems, E as getRawSurvey, G as unique, H as summarizeOutlineNode, I as resolveItemSummary, K as buildExpressionMutationBatch, L as sliceWithCursor, M as mapOutlineById, N as mapRawItemsById, O as itemConfigDetailsSchema, P as normalizeText, R as summarizeFocusItemNode, T as getItemIndexEntries, U as summarizeRawItem, W as translationEntrySchema, X as getSurveyAssistantExpressionDiagnosticGuidance, Y as analyzeSurveyAssistantExpressionReferences, a as collectExpressionMutationRecoveryTargets, b as buildItemTranslationEntries, c as auditSurvey, f as createSurveyChangeSet, h as normalizeSurveyChangeOperations, i as collectExpressionMutationRecoveryRefinements, j as mapItemIndexById, k as itemMatchesQuery, nt as createAssistantSurvey, o as collectMalformedSurveyOperationRecoveryTargets, q as expressionMutationSchema, rt as getAssistantVisibilityIssues, s as collectSurveyOperationRecoveryTargets, v as buildConfigSearchText, w as findDirectChildrenByParentId, x as clampLimit, y as buildItemConfigDetails, z as summarizeItemById } from "./engine-D950TfvC.mjs";
|
|
5
|
+
import { c as getDeterministicCreateElementTypes, l as inspectAssistantElement, u as resolveAssistantCapabilityReference } from "./default-D22AKTqE.mjs";
|
|
6
|
+
import { r as getSurveyAssistantEngineContext, s as requireSurveyAssistantTurnState } from "./request-context-KVh8VjgH.mjs";
|
|
7
|
+
import { a as surveyAuthoringReferences, i as surveyAuthoringReferenceTopics, r as getBuiltInElementTypeAuthoringReference, t as builtInSurveyAuthoringElementTypes } from "./authoring-references-CmwLc_C8.mjs";
|
|
6
8
|
import { z } from "zod";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
+
import { stringRecord } from "@case-framework/survey-core/schema";
|
|
10
|
+
import { SURVEY_EDITOR_ITEM_COLORS } from "@case-framework/survey-core/editor/colors";
|
|
11
|
+
import { JsonPatchError, SURVEY_LOCALIZATION_SURFACES as SURVEY_LOCALIZATION_SURFACES$1, encodeJsonPointerSegment, getContentPlainText, getLocalizationEntryKey, getValueAtJsonPointer, hasLocalizationText, listLocalizationEntries, ownValue, schemaAnnotation, setOwnValue } from "@case-framework/survey-core";
|
|
12
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
13
|
import { createTool, isValidationError } from "@mastra/core/tools";
|
|
10
14
|
//#region src/server/operation-contract-reference.ts
|
|
11
15
|
const isSurveyOperationKind = (value) => SURVEY_OPERATION_KINDS.includes(value);
|
|
@@ -19,74 +23,97 @@ const summarizeLargeSchema = (schema, propertyName) => {
|
|
|
19
23
|
description: `${description} The deeply nested schema is omitted from this compact reference; load the dedicated authoring reference when this structured value is needed.`
|
|
20
24
|
};
|
|
21
25
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
/** Compact explanatory material without deleting the grammar behind recursive references. */
|
|
27
|
+
const compactJsonSchema = (root) => {
|
|
28
|
+
const targets = /* @__PURE__ */ new Set();
|
|
29
|
+
const collect = (value) => {
|
|
30
|
+
if (!value || typeof value !== "object") return;
|
|
31
|
+
if ("$ref" in value && typeof value.$ref === "string" && value.$ref.startsWith("#")) targets.add(decodeURIComponent(value.$ref.slice(1)));
|
|
32
|
+
Object.values(value).forEach(collect);
|
|
33
|
+
};
|
|
34
|
+
collect(root);
|
|
35
|
+
const segment = (key) => String(key).replace(/~/g, "~0").replace(/\//g, "~1");
|
|
36
|
+
const compact = (value, pointer = "", propertyName, depth = 0) => {
|
|
37
|
+
if (targets.has(pointer)) return value;
|
|
38
|
+
if (Array.isArray(value)) return value.map((entry, index) => compact(entry, `${pointer}/${index}`, propertyName, depth + 1));
|
|
39
|
+
if (!isRecord$2(value)) return value;
|
|
40
|
+
if (typeof value.$ref === "string") return value;
|
|
41
|
+
if (![...targets].some((target) => target.startsWith(`${pointer}/`)) && depth >= 3 && JSON.stringify(value).length > 12e3) return summarizeLargeSchema(value, propertyName);
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const key of [
|
|
44
|
+
"type",
|
|
45
|
+
"const",
|
|
46
|
+
"enum",
|
|
47
|
+
"description",
|
|
48
|
+
"minimum",
|
|
49
|
+
"maximum",
|
|
50
|
+
"exclusiveMinimum",
|
|
51
|
+
"exclusiveMaximum",
|
|
52
|
+
"multipleOf",
|
|
53
|
+
"pattern",
|
|
54
|
+
"format",
|
|
55
|
+
"minLength",
|
|
56
|
+
"maxLength",
|
|
57
|
+
"minItems",
|
|
58
|
+
"maxItems",
|
|
59
|
+
"uniqueItems",
|
|
60
|
+
"minProperties",
|
|
61
|
+
"maxProperties",
|
|
62
|
+
"required",
|
|
63
|
+
"additionalProperties"
|
|
64
|
+
]) if (value[key] !== void 0) result[key] = value[key];
|
|
65
|
+
if (isRecord$2(value.properties)) result.properties = Object.fromEntries(Object.entries(value.properties).map(([key, schema]) => [key, compact(schema, `${pointer}/properties/${segment(key)}`, key, depth + 1)]));
|
|
66
|
+
if (value.items !== void 0) result.items = compact(value.items, `${pointer}/items`, propertyName, depth + 1);
|
|
67
|
+
for (const key of [
|
|
68
|
+
"oneOf",
|
|
69
|
+
"anyOf",
|
|
70
|
+
"allOf"
|
|
71
|
+
]) if (Array.isArray(value[key])) result[key] = value[key].map((entry, index) => compact(entry, `${pointer}/${key}/${index}`, propertyName, depth + 1));
|
|
72
|
+
for (const key of ["$defs", "definitions"]) if (value[key] !== void 0) result[key] = value[key];
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
return compact(root);
|
|
46
76
|
};
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`## Item capability: \`${manifest.itemType}\``,
|
|
53
|
-
"",
|
|
54
|
-
`${manifest.displayName}: ${manifest.description ?? "No additional description."}`,
|
|
55
|
-
`Creation mode: ${manifest.create}.`,
|
|
56
|
-
`Supported operations: ${manifest.supportedOperations.join(", ")}.`,
|
|
77
|
+
const formatElementCapabilityProfile = (manifest, kind) => {
|
|
78
|
+
return [
|
|
79
|
+
`## Element capability: ${manifest.elementType}`,
|
|
80
|
+
`${manifest.displayName}: ${manifest.description ?? ""}`,
|
|
81
|
+
`Creation mode: ${manifest.create}. Supported operations: ${manifest.supportedOperations.join(", ")}.`,
|
|
57
82
|
manifest.authoring?.compactHint,
|
|
58
|
-
manifest.authoring?.configSummary
|
|
59
|
-
manifest.creationPresets?.length ? `
|
|
60
|
-
|
|
61
|
-
patternedSurfaces.length > 0 ? `Dynamic content surfaces: ${patternedSurfaces.map((surface) => `${surface.key} (format=${surface.format})`).join(", ")}.` : void 0,
|
|
62
|
-
(!kind || kind === "create-item") && requiredSurfaces.length > 0 ? `Required on creation: ${requiredSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
|
|
63
|
-
manifest.referenceTopics?.length ? `Load dedicated guidance with survey_authoring_reference using these exact arguments: ${manifest.referenceTopics.map((topic) => JSON.stringify({
|
|
83
|
+
manifest.authoring?.configSummary,
|
|
84
|
+
manifest.creationPresets?.length ? `Creation presets: ${manifest.creationPresets.map((preset) => preset.id).join(", ")}.` : void 0,
|
|
85
|
+
manifest.referenceTopics?.length ? `Additional reference topics: ${manifest.referenceTopics.join(", ")}. Load a topic with survey_authoring_reference: ${manifest.referenceTopics.map((topic) => JSON.stringify({
|
|
64
86
|
topic,
|
|
65
|
-
|
|
66
|
-
})).join("; ")}.` : void 0
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return lines.join("\n");
|
|
87
|
+
elementType: manifest.elementType
|
|
88
|
+
})).join("; ")}.` : void 0,
|
|
89
|
+
"Configuration, identity, expressions and owned components come from the trusted registry's canonical schema. Inspect item details for actual owners, roles, slots and paths. Keep existing IDs during edits."
|
|
90
|
+
].filter((line) => Boolean(line)).join("\n\n");
|
|
70
91
|
};
|
|
71
|
-
const
|
|
72
|
-
"##
|
|
73
|
-
"This
|
|
92
|
+
const formatElementContract = (schema, detail = "compact") => [
|
|
93
|
+
"## Canonical element schema",
|
|
94
|
+
"This is the trusted element schema. patch-element-config paths are relative to its config property. The envelope also carries placement, response settings, prefills and conditions. Inspect owned roles for translated text; preserve surviving IDs.",
|
|
95
|
+
"Objects with x-survey-content-roles own localized content under their id. Use those exact role names and inline/block policies in ownerId/role translation entries; do not put translated wording in configuration fields.",
|
|
74
96
|
"```json",
|
|
75
97
|
JSON.stringify(z.toJSONSchema(schema, {
|
|
76
98
|
io: "input",
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
reused: "ref",
|
|
100
|
+
unrepresentable: "any",
|
|
101
|
+
override: ({ zodSchema, jsonSchema }) => {
|
|
102
|
+
const annotation = schemaAnnotation(zodSchema);
|
|
103
|
+
if (annotation?.kind === "owner") jsonSchema["x-survey-content-roles"] = annotation.roles;
|
|
104
|
+
}
|
|
105
|
+
}), null, detail === "full" ? 2 : void 0),
|
|
79
106
|
"```"
|
|
80
107
|
].join("\n\n");
|
|
81
108
|
const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
82
109
|
const { $schema: _schemaVersion, ...contract } = z.toJSONSchema(surveyOperationSchemasByKind[kind], {
|
|
83
110
|
io: "input",
|
|
84
|
-
reused: "
|
|
111
|
+
reused: "ref",
|
|
85
112
|
unrepresentable: "any"
|
|
86
113
|
});
|
|
87
114
|
const detail = options.detail ?? "compact";
|
|
88
115
|
const renderedContract = detail === "full" ? contract : compactJsonSchema(contract);
|
|
89
|
-
const
|
|
116
|
+
const elementCapability = options.elementCapability;
|
|
90
117
|
return [
|
|
91
118
|
`# \`${kind}\` operation contract`,
|
|
92
119
|
"",
|
|
@@ -94,13 +121,23 @@ const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
|
94
121
|
"the canonical post-normalization field shape. Cross-field and snapshot-dependent rules are",
|
|
95
122
|
"enforced when the proposal is validated. Follow focused authoring guidance when it recommends",
|
|
96
123
|
"a supported model-facing shorthand; otherwise use this canonical shape.",
|
|
97
|
-
detail === "compact" ? "This compact view preserves the operation structure while omitting deeply nested value schemas. Request detail \"full\" only for protocol debugging; load a focused rich-text, expression,
|
|
124
|
+
detail === "compact" ? "This compact view preserves the operation structure while omitting deeply nested value schemas. Request detail \"full\" only for protocol debugging; load a focused rich-text, expression, follow-up, or element-type reference for authoring guidance." : "This full view is intended for protocol debugging and may be large.",
|
|
98
125
|
"",
|
|
126
|
+
...[
|
|
127
|
+
"create-item",
|
|
128
|
+
"insert-element",
|
|
129
|
+
"insert-blocks",
|
|
130
|
+
"patch-element-config",
|
|
131
|
+
"patch-item-config",
|
|
132
|
+
"update-content",
|
|
133
|
+
"update-survey-translation"
|
|
134
|
+
].includes(kind) ? ["Localized content accepts exactly one of plainText, canonical content, or richTextBlocks shorthand. Load rich-text-content for the full block grammar. Translation entries use locale, ownerId and role; survey surfaces use their declared contentKey."] : [],
|
|
135
|
+
...kind === "survey-json-patch" ? ["Use this fallback only for exact inspected paths when no typed operation fits. It must be the only operation in a delta. Preserve existing localized values and schema identity; use survey_expression for expressions."] : [],
|
|
99
136
|
"```json",
|
|
100
|
-
JSON.stringify(renderedContract, null, 2),
|
|
137
|
+
JSON.stringify(renderedContract, null, detail === "full" ? 2 : void 0),
|
|
101
138
|
"```",
|
|
102
|
-
...
|
|
103
|
-
...options.
|
|
139
|
+
...elementCapability ? ["", formatElementCapabilityProfile(elementCapability, kind)] : [],
|
|
140
|
+
...options.elementSchema && (kind === "patch-element-config" || kind === "insert-element" || kind === "insert-preset" || kind === "create-item") ? [formatElementContract(options.elementSchema, detail)] : []
|
|
104
141
|
].join("\n");
|
|
105
142
|
};
|
|
106
143
|
//#endregion
|
|
@@ -178,7 +215,7 @@ const TOOL_ID$6 = "survey_authoring_reference";
|
|
|
178
215
|
const topicSchema = z.string().min(1).max(120);
|
|
179
216
|
const inputSchema$10 = z.object({
|
|
180
217
|
topic: topicSchema.describe("The narrow authoring reference to load."),
|
|
181
|
-
|
|
218
|
+
elementType: z.string().min(1).max(120).optional().describe("Installed element type to focus. With topic \"element-types\", returns only that element type section instead of the combined reference."),
|
|
182
219
|
operationKind: z.string().min(1).max(120).optional().describe("For topic assistant-operations, return only this operation's generated canonical field contract."),
|
|
183
220
|
detail: z.enum(SURVEY_OPERATION_CONTRACT_DETAIL_LEVELS).optional().describe("Contract detail for assistant-operations. Use \"compact\" for authoring and \"full\" only for protocol debugging."),
|
|
184
221
|
reason: z.string().max(500).optional().describe("Short reason this reference is needed.")
|
|
@@ -186,7 +223,7 @@ const inputSchema$10 = z.object({
|
|
|
186
223
|
const outputSchema$8 = z.object({
|
|
187
224
|
ok: z.boolean(),
|
|
188
225
|
topic: topicSchema,
|
|
189
|
-
|
|
226
|
+
elementType: z.string().optional(),
|
|
190
227
|
operationKind: z.string().optional(),
|
|
191
228
|
detail: z.enum(SURVEY_OPERATION_CONTRACT_DETAIL_LEVELS).optional(),
|
|
192
229
|
title: z.string(),
|
|
@@ -198,7 +235,7 @@ const formatCanonicalNameRecovery = (input, candidates, noun) => {
|
|
|
198
235
|
};
|
|
199
236
|
const surveyAuthoringReferenceTool = createTool({
|
|
200
237
|
id: TOOL_ID$6,
|
|
201
|
-
description: "Load one narrow CASE authoring reference. With topic \"
|
|
238
|
+
description: "Load one narrow CASE authoring reference. With topic \"element-types\", pass elementType to load only that type. For a survey_change operation, use topic \"assistant-operations\" with operationKind and optionally elementType to compose the structural contract with that installed element capability. Compact detail is the authoring default; full detail is only for protocol debugging.",
|
|
202
239
|
inputSchema: inputSchema$10,
|
|
203
240
|
outputSchema: outputSchema$8,
|
|
204
241
|
mcp: { annotations: {
|
|
@@ -211,12 +248,20 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
211
248
|
execute: async (inputData, context) => {
|
|
212
249
|
const contractDetail = inputData.detail ?? "compact";
|
|
213
250
|
const engineContext = getSurveyAssistantEngineContext(context?.requestContext);
|
|
214
|
-
const
|
|
215
|
-
const
|
|
251
|
+
const elementCapability = inputData.elementType ? engineContext?.trustedCapabilities?.byElementType.get(inputData.elementType)?.manifest ?? engineContext?.capabilities.elementTypes.find((entry) => entry.elementType === inputData.elementType) : void 0;
|
|
252
|
+
const availableElementTypes = engineContext ? engineContext.capabilities.elementTypes.map((entry) => entry.elementType) : builtInSurveyAuthoringElementTypes;
|
|
253
|
+
if (inputData.elementType && engineContext && !elementCapability) return {
|
|
254
|
+
ok: false,
|
|
255
|
+
topic: inputData.topic,
|
|
256
|
+
elementType: inputData.elementType,
|
|
257
|
+
operationKind: inputData.operationKind,
|
|
258
|
+
title: "Element capability unavailable",
|
|
259
|
+
content: `No installed survey element capability named "${inputData.elementType}" is available.` + formatCanonicalNameRecovery(inputData.elementType, availableElementTypes, "element type")
|
|
260
|
+
};
|
|
216
261
|
if (inputData.operationKind && inputData.topic !== "assistant-operations") return {
|
|
217
262
|
ok: false,
|
|
218
263
|
topic: inputData.topic,
|
|
219
|
-
|
|
264
|
+
elementType: inputData.elementType,
|
|
220
265
|
operationKind: inputData.operationKind,
|
|
221
266
|
title: "Operation contract unavailable",
|
|
222
267
|
content: "operationKind is supported only with topic assistant-operations."
|
|
@@ -230,62 +275,65 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
230
275
|
title: "Operation contract unavailable",
|
|
231
276
|
content: `No built-in survey_change operation named "${inputData.operationKind}" is available.` + formatCanonicalNameRecovery(inputData.operationKind, SURVEY_OPERATION_KINDS, "operation")
|
|
232
277
|
};
|
|
233
|
-
if (inputData.
|
|
278
|
+
if (inputData.elementType && !elementCapability && !getBuiltInElementTypeAuthoringReference(inputData.elementType)) return {
|
|
234
279
|
ok: false,
|
|
235
280
|
topic: inputData.topic,
|
|
236
|
-
|
|
281
|
+
elementType: inputData.elementType,
|
|
237
282
|
operationKind: inputData.operationKind,
|
|
238
283
|
detail: contractDetail,
|
|
239
284
|
title: "Item capability unavailable",
|
|
240
|
-
content: `No installed survey item capability named "${inputData.
|
|
285
|
+
content: `No installed survey item capability named "${inputData.elementType}" is available.` + formatCanonicalNameRecovery(inputData.elementType, availableElementTypes, "item type")
|
|
241
286
|
};
|
|
242
|
-
if (
|
|
287
|
+
if (elementCapability && !elementCapability.supportedOperations.includes(inputData.operationKind)) return {
|
|
243
288
|
ok: false,
|
|
244
289
|
topic: inputData.topic,
|
|
245
|
-
|
|
290
|
+
elementType: inputData.elementType,
|
|
246
291
|
operationKind: inputData.operationKind,
|
|
247
292
|
detail: contractDetail,
|
|
248
293
|
title: "Operation unsupported for item type",
|
|
249
|
-
content: `Item type "${
|
|
294
|
+
content: `Item type "${elementCapability.elementType}" does not support survey_change operation "${inputData.operationKind}".`
|
|
250
295
|
};
|
|
251
296
|
return {
|
|
252
297
|
ok: true,
|
|
253
298
|
topic: inputData.topic,
|
|
254
|
-
|
|
299
|
+
elementType: inputData.elementType,
|
|
255
300
|
operationKind: inputData.operationKind,
|
|
256
301
|
detail: contractDetail,
|
|
257
|
-
title:
|
|
302
|
+
title: elementCapability ? `${inputData.operationKind} for ${elementCapability.displayName}` : `${inputData.operationKind} operation contract`,
|
|
258
303
|
content: formatSurveyOperationContractReference(inputData.operationKind, {
|
|
259
304
|
detail: contractDetail,
|
|
260
|
-
|
|
261
|
-
|
|
305
|
+
elementCapability,
|
|
306
|
+
elementSchema: inputData.elementType ? engineContext?.trustedCapabilities?.registry.get(inputData.elementType).schema : void 0
|
|
262
307
|
})
|
|
263
308
|
};
|
|
264
309
|
}
|
|
265
310
|
const builtInEntry = surveyAuthoringReferenceTopics.includes(inputData.topic) ? surveyAuthoringReferences[inputData.topic] : void 0;
|
|
266
|
-
const capability = inputData.
|
|
267
|
-
const capabilityReference = inputData.
|
|
311
|
+
const capability = inputData.elementType ? engineContext?.trustedCapabilities?.byElementType.get(inputData.elementType) : void 0;
|
|
312
|
+
const capabilityReference = inputData.elementType ? resolveAssistantCapabilityReference({
|
|
268
313
|
capabilities: engineContext?.trustedCapabilities,
|
|
269
|
-
|
|
314
|
+
elementType: inputData.elementType,
|
|
270
315
|
topic: inputData.topic
|
|
271
316
|
}) : void 0;
|
|
272
|
-
const
|
|
273
|
-
if (inputData.
|
|
317
|
+
const focusedBuiltInElementTypeReference = inputData.topic === "element-types" && inputData.elementType ? getBuiltInElementTypeAuthoringReference(inputData.elementType) : void 0;
|
|
318
|
+
if (inputData.elementType && !focusedBuiltInElementTypeReference && !capability && !capabilityReference) return {
|
|
274
319
|
ok: false,
|
|
275
320
|
topic: inputData.topic,
|
|
276
|
-
|
|
321
|
+
elementType: inputData.elementType,
|
|
277
322
|
title: "Item capability unavailable",
|
|
278
|
-
content: `No installed survey item capability named "${inputData.
|
|
323
|
+
content: `No installed survey item capability named "${inputData.elementType}" is available.` + formatCanonicalNameRecovery(inputData.elementType, availableElementTypes, "item type")
|
|
279
324
|
};
|
|
280
325
|
const capabilityEntry = capabilityReference ? {
|
|
281
326
|
title: capabilityReference.title ?? `${capability?.manifest.displayName} authoring`,
|
|
282
327
|
content: capabilityReference.markdown
|
|
283
328
|
} : void 0;
|
|
284
|
-
const focusedCapabilityProfile = inputData.topic === "
|
|
285
|
-
title:
|
|
286
|
-
content:
|
|
329
|
+
const focusedCapabilityProfile = inputData.topic === "element-types" && elementCapability ? {
|
|
330
|
+
title: elementCapability.displayName,
|
|
331
|
+
content: formatElementCapabilityProfile(elementCapability)
|
|
287
332
|
} : void 0;
|
|
288
|
-
const baseEntry = inputData.topic === "
|
|
333
|
+
const baseEntry = inputData.topic === "element-types" && inputData.elementType ? focusedBuiltInElementTypeReference && focusedCapabilityProfile ? {
|
|
334
|
+
...focusedBuiltInElementTypeReference,
|
|
335
|
+
content: [focusedBuiltInElementTypeReference.content, focusedCapabilityProfile.content].join("\n\n")
|
|
336
|
+
} : focusedBuiltInElementTypeReference ?? focusedCapabilityProfile : builtInEntry;
|
|
289
337
|
const entry = baseEntry && capabilityEntry ? {
|
|
290
338
|
title: baseEntry.title,
|
|
291
339
|
content: baseEntry.content === capabilityEntry.content ? baseEntry.content : [
|
|
@@ -298,18 +346,18 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
298
346
|
if (!entry) return {
|
|
299
347
|
ok: false,
|
|
300
348
|
topic: inputData.topic,
|
|
301
|
-
|
|
349
|
+
elementType: inputData.elementType,
|
|
302
350
|
operationKind: inputData.operationKind,
|
|
303
351
|
title: "Reference unavailable",
|
|
304
|
-
content: (inputData.
|
|
352
|
+
content: (inputData.elementType ? `No reference named "${inputData.topic}" is installed for item type "${inputData.elementType}".` : `No built-in reference named "${inputData.topic}" is available.`) + formatCanonicalNameRecovery(inputData.topic, [...surveyAuthoringReferenceTopics, ...elementCapability?.referenceTopics ?? []], "reference")
|
|
305
353
|
};
|
|
306
354
|
return {
|
|
307
355
|
ok: true,
|
|
308
356
|
topic: inputData.topic,
|
|
309
|
-
|
|
357
|
+
elementType: inputData.elementType,
|
|
310
358
|
operationKind: inputData.operationKind,
|
|
311
359
|
title: entry.title,
|
|
312
|
-
content: inputData.topic === "
|
|
360
|
+
content: inputData.topic === "element-types" && inputData.elementType && engineContext?.trustedCapabilities ? `${entry.content}\n\n${formatElementContract(engineContext.trustedCapabilities.registry.get(inputData.elementType).schema)}` : entry.content
|
|
313
361
|
};
|
|
314
362
|
},
|
|
315
363
|
toModelOutput: (value) => toTextModelOutput(value, (output) => `# ${output.title}\n\n${output.content}`)
|
|
@@ -330,25 +378,18 @@ const summarizePendingRepairs = (event) => {
|
|
|
330
378
|
//#region src/server/tools/survey-change-tool.ts
|
|
331
379
|
const TOOL_ID$5 = "survey_change";
|
|
332
380
|
const OPERATIONS_DESCRIPTION = [
|
|
333
|
-
"Typed
|
|
334
|
-
"
|
|
335
|
-
"
|
|
336
|
-
"
|
|
337
|
-
"
|
|
338
|
-
"Use update-
|
|
339
|
-
"
|
|
340
|
-
|
|
341
|
-
"
|
|
342
|
-
"
|
|
343
|
-
"
|
|
344
|
-
"
|
|
345
|
-
`Use update-item-color only with a supported swatch. ${`Allowed editor item-color swatches: ${SURVEY_EDITOR_ITEM_COLORS.join(", ")}. Use one of these exact values for update-item-color; do not wait for validation feedback to choose a swatch.`}`,
|
|
346
|
-
"Use update-response-settings with naming and settings arrays for variable names, matrix coding components, option export codes and export formats. Load response-variables for the shared coding contract.",
|
|
347
|
-
"Use patch-item-config with itemId and plural patches whose JSON Pointer paths are relative to that item config root. Include related translations inside the operation. Load the focused item capability reference for its configuration and content keys.",
|
|
348
|
-
"Use update-placed-image only for an image already embedded in rich text in the live survey or validated turn draft. It can switch the placement between a local asset and an external URL and update its rendered ALT text, caption, width, height, maximum dimensions, and alignment. For a new item, prefer putting the canonical image block inside create-item.translations[].richTextBlocks. Do not use image asset metadata when the user asked to change one placed image.",
|
|
349
|
-
"When deleting referenced images and assets, include every reference-removal operation and remove-image-asset last in the same operations array.",
|
|
350
|
-
"A survey-json-patch proposal contains exactly one operation with kind survey-json-patch and one plural patches array containing every related raw edit. It must be the only operation in that survey_change call. Paths are rooted at the survey document, such as /surveyItems/<index>/config or /translations/<locale>; never start at /config or modify /$schema. Patch translation leaves rather than replacing existing locale or item-translation containers.",
|
|
351
|
-
"Keep each delta bounded; this is a per-call constraint, not a reason to end the turn. Successful chunks accumulate automatically, so substantial requests should continue with later deltas while capacity remains."
|
|
381
|
+
"Typed operations use \"kind\". Load survey_authoring_reference topic=assistant-operations with operationKind for the exact generated schema.",
|
|
382
|
+
"Use insert-preset with elementType, presetId, locale and target {parentItemId,index?} for a familiar standalone question; target {layoutId,index?} adds it to an existing layout. Presets materialize fresh IDs once, before staging. Inspect the result before later edits.",
|
|
383
|
+
"For authored composition use create-item with a canonical content/group/page-break item, insert-element into a layout or insert-blocks into a body. Include ownerId/role/locale translations in the same operation. Sections are full-width vertical blocks, with one section level and one follow-up level enforced independently.",
|
|
384
|
+
"Use update-content with ref {scope:\"content\",ownerId,role} or a survey ref, locale and exactly one of plainText, content or richTextBlocks shorthand. content:null clears a value. Text belongs to declared owners, never flattened item keys. Keep all locales intact when moving content.",
|
|
385
|
+
"Use patch-element-config for choice options, matrix rows/cells and element settings. Paths are relative to the inspected config. Use move-element/move-block for moves and explicit ruleTransfer when required. Use survey_expression for expressions; never route them through generic configuration patches.",
|
|
386
|
+
"Use update-response-settings for variable names, matrix naming, option codes and exports. Never rename a single computed matrix slot.",
|
|
387
|
+
"Every reorder supplies all current children exactly once. Use move-item for flow moves and reorder-items for flow sibling order.",
|
|
388
|
+
`Allowed editor item-color swatches: ${SURVEY_EDITOR_ITEM_COLORS.join(", ")}. Use one of these exact values for update-item-color; do not wait for validation feedback to choose a swatch.`,
|
|
389
|
+
"Image placement edits use update-placed-image with the same owner/survey ref. For imported bytes use survey_asset; remove placements before removing an asset.",
|
|
390
|
+
"Register an empty locale only if explicitly requested. Translation writes can introduce a locale; remove-survey-locale deletes its translations.",
|
|
391
|
+
"A raw survey-json-patch must be the only operation in a delta. Inspect its exact pointers first, preserve existing translations, and never change schema identity. All nested elements still require trusted authoring grants.",
|
|
392
|
+
"Successful deltas accumulate in the same turn draft; rejected deltas leave it unchanged and require repair of the same target. The live editor changes once, through its normal undo path, after successful completion. An empty operations array is only for a linked validator-confirmed no-op acknowledgment."
|
|
352
393
|
].join(" ");
|
|
353
394
|
const inputSchema$9 = z.object({
|
|
354
395
|
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
@@ -375,39 +416,8 @@ const rejectedOperationParseEvent = ({ issueText, summary, turnId }) => ({
|
|
|
375
416
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
376
417
|
});
|
|
377
418
|
const describeOperationTarget = (operation) => {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
case "update-placed-image": return `${operation.kind} ${operation.locale}:${operation.itemId}:${operation.contentKey}#${operation.imageIndex}`;
|
|
381
|
-
case "update-item-label":
|
|
382
|
-
case "update-item-color":
|
|
383
|
-
case "patch-item-config":
|
|
384
|
-
case "remove-item":
|
|
385
|
-
case "move-item": return `${operation.kind} ${operation.itemId}`;
|
|
386
|
-
case "upsert-form-field-group": return `${operation.kind} ${operation.target.itemId}:${operation.group.id}`;
|
|
387
|
-
case "remove-form-field-group": return `${operation.kind} ${operation.target.itemId}:${operation.groupId}`;
|
|
388
|
-
case "reorder-form-field-groups": return `${operation.kind} ${operation.target.itemId}`;
|
|
389
|
-
case "upsert-form-field": return `${operation.kind} ${operation.target.itemId}:${operation.field.id}`;
|
|
390
|
-
case "remove-form-field":
|
|
391
|
-
case "move-form-field": return `${operation.kind} ${operation.target.itemId}:${operation.fieldId}`;
|
|
392
|
-
case "set-embedded-form":
|
|
393
|
-
case "remove-embedded-form": return `${operation.kind} ${operation.itemId}:${operation.optionId}`;
|
|
394
|
-
case "add-choice-option": return `${operation.kind} ${operation.itemId}:${operation.option.id ?? operation.option.code ?? operation.option.label}`;
|
|
395
|
-
case "update-choice-option-label":
|
|
396
|
-
case "remove-choice-option": return `${operation.kind} ${operation.itemId}:${operation.optionId}`;
|
|
397
|
-
case "reorder-choice-options": return `${operation.kind} ${operation.itemId}`;
|
|
398
|
-
case "reorder-items": return `${operation.kind} ${operation.parentItemId}`;
|
|
399
|
-
case "update-response-settings":
|
|
400
|
-
case "update-survey-metadata":
|
|
401
|
-
case "update-survey-pagination": return operation.kind;
|
|
402
|
-
case "update-survey-translation": return `${operation.kind} ${operation.locale}:${operation.surface}:${operation.contentKey}`;
|
|
403
|
-
case "add-survey-locale":
|
|
404
|
-
case "remove-survey-locale": return `${operation.kind} ${operation.locale}`;
|
|
405
|
-
case "set-image-asset":
|
|
406
|
-
case "update-image-asset-metadata":
|
|
407
|
-
case "remove-image-asset": return `${operation.kind} ${operation.assetId}`;
|
|
408
|
-
case "create-item": return `${operation.kind} ${operation.item.id ?? operation.item.metadata?.itemLabel ?? operation.item.id ?? operation.item.itemType}`;
|
|
409
|
-
case "survey-json-patch": return `${operation.kind} ${operation.patches.map((patch) => patch.path).join(", ")}`;
|
|
410
|
-
}
|
|
419
|
+
const target = "itemId" in operation ? operation.itemId : "elementId" in operation ? operation.elementId : "ownerId" in operation ? operation.ownerId : "ref" in operation ? JSON.stringify(operation.ref) : "assetId" in operation ? operation.assetId : "item" in operation ? operation.item.id : "target" in operation ? JSON.stringify(operation.target) : "bodyOwnerId" in operation ? operation.bodyOwnerId : "layoutId" in operation ? operation.layoutId : "parentItemId" in operation ? operation.parentItemId : "locale" in operation ? operation.locale : "survey";
|
|
420
|
+
return `${operation.kind} ${target}`;
|
|
411
421
|
};
|
|
412
422
|
const summarizeValidation = (event) => {
|
|
413
423
|
const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} survey change: ${event.summary}`];
|
|
@@ -426,7 +436,7 @@ const summarizeValidation = (event) => {
|
|
|
426
436
|
lines.push(`Canonical post-change preview (mutation targets first, then affected parents; projected fields only): ${event.postChange.itemCount} survey item(s); locales ${event.postChange.locales.length}/${event.postChange.localeCount} shown; targets ${event.postChange.touchedTargets.length}/${event.postChange.targetCount} shown; available touched-item snapshots ${event.postChange.touchedItems.length}/${event.postChange.availableTouchedItemCount} shown${unavailableItemCount > 0 ? `; ${unavailableItemCount} touched item target(s) are absent after the change` : ""}.`);
|
|
427
437
|
lines.push(`Target preview: ${event.postChange.touchedTargets.join(", ") || "none"}. Absence from this bounded projection does not prove that a field or target is absent from the canonical draft.`);
|
|
428
438
|
for (const item of event.postChange.touchedItems) {
|
|
429
|
-
const translations = item.translations.map(({
|
|
439
|
+
const translations = item.translations.map(({ ownerId, role, locale, textLength, textPreview, truncated }) => truncated ? `${locale}:${ownerId}/${role}=${JSON.stringify(`${textPreview}…`)} [truncated preview; ${textLength} characters total]` : `${locale}:${ownerId}/${role}=${JSON.stringify(textPreview)}`).join("; ");
|
|
430
440
|
lines.push(`- ${item.breadcrumb} (${item.itemType}, id ${item.itemId}); response slots ${item.responseSlots.length}/${item.responseSlotCount}: ${item.responseSlots.join(", ") || "none"}; translations ${item.translations.length}/${item.translationCount}: ${translations || "none"}`);
|
|
431
441
|
if (item.placement) {
|
|
432
442
|
const p = item.placement;
|
|
@@ -448,7 +458,7 @@ const summarizeValidation = (event) => {
|
|
|
448
458
|
const location = [
|
|
449
459
|
finding.itemId,
|
|
450
460
|
finding.locale,
|
|
451
|
-
finding.
|
|
461
|
+
finding.localizationRefs ? JSON.stringify(finding.localizationRefs) : void 0
|
|
452
462
|
].filter(Boolean).join(":");
|
|
453
463
|
lines.push(`- ${finding.severity} ${finding.code}${location ? ` (${location})` : ""}: ${finding.messagePreview}${finding.truncated ? `… [truncated preview; ${finding.messageLength} characters total]` : ""}`);
|
|
454
464
|
}
|
|
@@ -459,7 +469,6 @@ const summarizeValidation = (event) => {
|
|
|
459
469
|
lines.push("Issues:");
|
|
460
470
|
for (const issue of event.validation.issues) lines.push(`- ${issue.code}: ${issue.message}`);
|
|
461
471
|
const issueCodes = new Set(event.validation.issues.map((issue) => issue.code));
|
|
462
|
-
if (issueCodes.has("missing-create-content") || issueCodes.has("missing-field-groups-wrapper") || issueCodes.has("missing-choice-options") || issueCodes.has("missing-choice-option-labels") || issueCodes.has("missing-form-field-content") || issueCodes.has("missing-form-select-option-labels") || issueCodes.has("missing-form-field-labels") || issueCodes.has("missing-embedded-form-field-labels")) lines.push("Recovery hint: load the assistant-operations reference, then retry only the rejected delta. Put visible text in top-level translations[] beside item, config inside item.config, choice options in item.config.options, and form fields inside item.config.fieldGroups[].fields. Every choice option id needs a matching top-level translation whose contentKey is options.<optionId>.label.");
|
|
463
472
|
if (issueCodes.has("too-many-operations")) lines.push("Recovery hint: keep the same inspected survey context and split this rejected delta into smaller survey_change calls. Each successful call extends the same turn draft.");
|
|
464
473
|
if (issueCodes.has("item-already-in-target-parent")) lines.push("Recovery hint: remove unchanged same-parent move-item operations. To change sibling order within one parent, inspect its complete current children and use reorder-items instead.");
|
|
465
474
|
}
|
|
@@ -526,7 +535,7 @@ const inputSchema$8 = z.object({
|
|
|
526
535
|
limit: z.number().int().min(1).max(80).optional().describe("Context action only: rows per catalog page; defaults to 80."),
|
|
527
536
|
includeRawLocations: z.boolean().optional().describe("Context action only: include existing expression locations by raw item."),
|
|
528
537
|
summary: z.string().max(500).optional().describe("Prepare action only: required user-facing summary."),
|
|
529
|
-
mutations: z.array(z.unknown()).min(1).max(MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL).optional().describe(`Prepare action only: an array of 1-${MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL} canonical expression mutation objects from the loaded expressions reference. Example entry: {"kind":"set-
|
|
538
|
+
mutations: z.array(z.unknown()).min(1).max(MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL).optional().describe(`Prepare action only: an array of 1-${MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL} canonical expression mutation objects from the loaded expressions reference. Example entry: {"kind":"set-condition","ownerId":"<existing-owner-id>","property":"visibility","expression":{"type":"function","functionName":"eq","arguments":[...]}}. expression is CASE expression JSON, never an expression-language snippet.`)
|
|
530
539
|
}).strict().superRefine((value, ctx) => {
|
|
531
540
|
if (value.action === "context") {
|
|
532
541
|
if (value.summary?.trim()) ctx.addIssue({
|
|
@@ -556,11 +565,18 @@ const contextOutputSchema = z.object({
|
|
|
556
565
|
ok: z.boolean(),
|
|
557
566
|
action: z.literal("context"),
|
|
558
567
|
modelSummary: z.string(),
|
|
559
|
-
listOrdering:
|
|
568
|
+
listOrdering: stringRecord(z.string(), z.string()).describe("Meaning of each returned list. Expression references are not positional."),
|
|
560
569
|
nextCursor: z.string().optional(),
|
|
561
570
|
totalResponseSlots: z.number().int().nonnegative().optional(),
|
|
562
571
|
surveyResponseSlotCount: z.number().int().nonnegative().optional(),
|
|
563
572
|
totalExpressionLocations: z.number().int().nonnegative().optional(),
|
|
573
|
+
totalStateTargets: z.number().int().nonnegative().optional(),
|
|
574
|
+
stateTargets: z.array(z.object({
|
|
575
|
+
targetRef: z.object({ ownerId: z.string() }),
|
|
576
|
+
state: z.literal("disabled"),
|
|
577
|
+
itemId: z.string(),
|
|
578
|
+
kind: z.string()
|
|
579
|
+
})).optional(),
|
|
564
580
|
unresolvedItemIds: z.array(z.string()).optional(),
|
|
565
581
|
responseSlots: z.array(z.object({
|
|
566
582
|
ref: z.object({
|
|
@@ -584,7 +600,6 @@ const createIssue = ({ severity = "error", ...input }) => ({
|
|
|
584
600
|
severity,
|
|
585
601
|
...input
|
|
586
602
|
});
|
|
587
|
-
const buildResponseSlotTypes = (snapshot) => snapshot.responseSlotTypes;
|
|
588
603
|
const rejectedEvent = ({ issues, summary, turnId, warnings = [] }) => ({
|
|
589
604
|
protocolVersion: 1,
|
|
590
605
|
id: randomUUID(),
|
|
@@ -620,36 +635,39 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
620
635
|
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
621
636
|
const resolvedItems = input.itemIds?.map((identifier) => ({
|
|
622
637
|
identifier,
|
|
623
|
-
itemId: snapshot.inspection.itemIndex.find((entry) => entry.itemId === identifier
|
|
638
|
+
itemId: snapshot.inspection.itemIndex.find((entry) => entry.itemId === identifier)?.itemId
|
|
624
639
|
}));
|
|
625
640
|
const itemFilter = resolvedItems?.length ? new Set(resolvedItems.flatMap((entry) => entry.itemId ? [entry.itemId] : [])) : void 0;
|
|
626
641
|
const unresolvedItemIds = resolvedItems?.filter((entry) => !entry.itemId).map((entry) => entry.identifier) ?? [];
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
|
|
642
|
+
const allResponseSlots = snapshot.responseVariables.filter((slot) => !itemFilter || itemFilter.has(slot.itemId)).flatMap((slot) => [{
|
|
643
|
+
ref: {
|
|
644
|
+
slotId: slot.slotId,
|
|
645
|
+
method: "get"
|
|
646
|
+
},
|
|
647
|
+
valueType: slot.valueType,
|
|
648
|
+
variableName: slot.variableName,
|
|
649
|
+
itemId: slot.itemId
|
|
650
|
+
}, {
|
|
651
|
+
ref: {
|
|
652
|
+
slotId: slot.slotId,
|
|
653
|
+
method: "isDefined"
|
|
654
|
+
},
|
|
655
|
+
valueType: "boolean",
|
|
656
|
+
variableName: slot.variableName,
|
|
657
|
+
itemId: slot.itemId
|
|
658
|
+
}]);
|
|
659
|
+
const allStateTargets = [...snapshot.model.owners.values()].filter((owner) => !itemFilter || itemFilter.has(owner.itemId)).map((owner) => ({
|
|
660
|
+
targetRef: { ownerId: owner.id },
|
|
661
|
+
itemId: owner.itemId,
|
|
662
|
+
kind: owner.kind,
|
|
663
|
+
state: "disabled"
|
|
664
|
+
}));
|
|
641
665
|
const offset = Number(input.cursor ?? 0);
|
|
642
666
|
const limit = input.limit ?? 80;
|
|
643
667
|
const responseSlots = allResponseSlots.slice(offset, offset + limit);
|
|
644
668
|
const allExpressionLocations = input.action === "context" && input.includeRawLocations === true ? rawItems.map((item, rawIndex) => {
|
|
645
669
|
if (itemFilter && !itemFilter.has(item.id)) return;
|
|
646
|
-
const locations = [
|
|
647
|
-
item.displayConditions?.root ? "displayConditions.root" : void 0,
|
|
648
|
-
...Object.keys(item.displayConditions?.components ?? {}).map((componentId) => `displayConditions.components.${componentId}`),
|
|
649
|
-
...Object.keys(item.disabledConditions?.components ?? {}).map((componentId) => `disabledConditions.components.${componentId}`),
|
|
650
|
-
...Object.keys(item.validations ?? {}).map((validationKey) => `validations.${validationKey}`),
|
|
651
|
-
...(item.prefills ?? []).map((prefill) => `prefills.${prefill.id}`)
|
|
652
|
-
].filter((location) => Boolean(location));
|
|
670
|
+
const locations = snapshot.model.expressions.filter((entry) => entry.path[0] === "surveyItems" && entry.path[1] === rawIndex).map((entry) => `/${entry.path.map((part) => encodeJsonPointerSegment(String(part))).join("/")}`);
|
|
653
671
|
return {
|
|
654
672
|
itemId: item.id,
|
|
655
673
|
editorName: names.get(item.id) ?? item.id,
|
|
@@ -659,7 +677,7 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
659
677
|
};
|
|
660
678
|
}).filter((entry) => Boolean(entry)) : void 0;
|
|
661
679
|
const expressionLocations = allExpressionLocations?.slice(offset, offset + limit);
|
|
662
|
-
const totalAvailable = Math.max(allResponseSlots.length, allExpressionLocations?.length ?? 0);
|
|
680
|
+
const totalAvailable = Math.max(allResponseSlots.length, allStateTargets.length, allExpressionLocations?.length ?? 0);
|
|
663
681
|
const nextCursor = offset + limit < totalAvailable ? String(offset + limit) : void 0;
|
|
664
682
|
return {
|
|
665
683
|
ok: true,
|
|
@@ -667,31 +685,36 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
667
685
|
modelSummary: [
|
|
668
686
|
"Expression capability loaded.",
|
|
669
687
|
"Use prepare mutations for display conditions, disabled conditions, validations, prefills, and template values.",
|
|
670
|
-
"
|
|
688
|
+
"Visibility, disabled and validation expressions must return boolean.",
|
|
671
689
|
"Use response refs exactly as listed and check their value types before choosing functions.",
|
|
690
|
+
"State nodes use {type:'ownerState',state:'disabled',targetRef:{ownerId}} (isDisabled in the editor). They include inherited disabling but do not mean hidden, missing or unchangeable due to selection limits. State references participate in the same dependency graph and cycle checks as answers.",
|
|
691
|
+
"Disabled answers remain active and are submitted. For selected-and-enabled behavior combine selection with not(isDisabled(option)); a disabled ancestor also makes that condition false. Use ordinary selection for read-only prefilled answers that must still drive branching.",
|
|
672
692
|
"An isDefined ref means only that an active answer exists; it never means a particular answer such as Yes, selected, eligible, or has children.",
|
|
673
693
|
"When the condition names or implies a choice, inspect the source choice item for the exact option id, then compare its get ref with that reference using eq or list_contains.",
|
|
674
|
-
"Visibility must be acyclic, including item, ancestor, component and computed-response dependencies. Prefills do not permit cycles. Use an independent controlling answer or serialized context value. Hidden answers are retained but inactive: get is undefined and isDefined is false.
|
|
694
|
+
"Visibility must be acyclic, including item, ancestor, component and computed-response dependencies. Prefills do not permit cycles. Use an independent controlling answer or serialized context value. Hidden answers are retained but inactive: get is undefined and isDefined is false. An absent visibility condition shows its target; an evaluated missing/nonboolean condition hides it. All expressions use the canonical JSON grammar.",
|
|
675
695
|
"For choice options: use eq(response reference, reference const) when the slot type is reference; use list_contains(response reference[], reference const) only when the slot type is reference[].",
|
|
676
696
|
"Response-slot order: set-like deterministic snapshot construction order; it has no survey or respondent-visible meaning.",
|
|
677
|
-
itemFilter ? `Response-slot scope: filtered to itemIds=${JSON.stringify(input.itemIds)}; ${allResponseSlots.length} matching of ${
|
|
697
|
+
itemFilter ? `Response-slot scope: filtered to itemIds=${JSON.stringify(input.itemIds)}; ${allResponseSlots.length} matching of ${snapshot.responseVariables.length * 2} survey-wide slots. References outside this filter are omitted, not absent. To find controlling questions elsewhere, call survey_expression action=context without itemIds or cursor.` : `Response-slot scope: survey-wide (${snapshot.responseVariables.length * 2} slots).`,
|
|
698
|
+
`State targets (${allStateTargets.length}): ${JSON.stringify(allStateTargets.slice(offset, offset + limit))}`,
|
|
678
699
|
`Response slots (${responseSlots.length}/${allResponseSlots.length}, offset ${offset}): ${responseSlots.map((slot) => `${JSON.stringify(slot.ref)}${slot.valueType ? `:${slot.valueType}` : ""}`).join(", ")}`,
|
|
679
700
|
nextCursor ? `More expression context is available. Call survey_expression action=context with cursor="${nextCursor}" and the same itemIds, includeRawLocations, and limit. Restart without a cursor after a draft mutation.` : "Expression context pagination complete for this scope.",
|
|
680
701
|
unresolvedItemIds.length > 0 ? `Unresolved item identifiers: ${JSON.stringify(unresolvedItemIds)}. Inspect current context or search-items to resolve them; no references for these targets are implied.` : void 0,
|
|
681
|
-
"Functions: and, or, not, eq, gt, gte, lt, lte, in_range, list_contains, sum, min, max, str_eq, date_eq.",
|
|
702
|
+
"Functions: and, or, not, eq, gt, gte, lt, lte, in_range, list_contains, list_count, if, sum, min, max, str_eq, date_eq.",
|
|
682
703
|
expressionLocations ? `Existing expression locations: ${expressionLocations.filter((entry) => entry.locations.length > 0).map((entry) => `${entry.editorName}(${entry.itemId}) -> ${entry.locations.join("|")}`).join("; ") || "none"}` : void 0
|
|
683
704
|
].filter((line) => Boolean(line)).join("\n"),
|
|
684
705
|
totalResponseSlots: allResponseSlots.length,
|
|
685
|
-
surveyResponseSlotCount:
|
|
706
|
+
surveyResponseSlotCount: snapshot.responseVariables.length * 2,
|
|
686
707
|
...allExpressionLocations ? { totalExpressionLocations: allExpressionLocations.length } : {},
|
|
687
708
|
...nextCursor ? { nextCursor } : {},
|
|
688
709
|
...unresolvedItemIds.length ? { unresolvedItemIds } : {},
|
|
689
710
|
listOrdering: {
|
|
690
711
|
responseSlots: "Set-like deterministic snapshot construction order; position has no survey or respondent-visible meaning.",
|
|
691
712
|
expressionLocations: "Stored surveyItems array order; locations within an item are grouped by expression surface, not ranked or respondent-visible order.",
|
|
692
|
-
"expressionLocations[].locations": "
|
|
713
|
+
"expressionLocations[].locations": "Canonical schema traversal order; paths locate conditions, rules, prefills and element expressions."
|
|
693
714
|
},
|
|
694
715
|
responseSlots,
|
|
716
|
+
stateTargets: allStateTargets.slice(offset, offset + limit),
|
|
717
|
+
totalStateTargets: allStateTargets.length,
|
|
695
718
|
...expressionLocations ? { expressionLocations } : {}
|
|
696
719
|
};
|
|
697
720
|
};
|
|
@@ -755,7 +778,7 @@ const surveyExpressionTool = createTool({
|
|
|
755
778
|
}), collectExpressionMutationRecoveryTargets(input.mutations, snapshot));
|
|
756
779
|
const prepareInput = prepareParse.data;
|
|
757
780
|
const addressedTargets = collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot);
|
|
758
|
-
const { issues, patches } = buildExpressionMutationBatch(prepareInput.mutations, snapshot);
|
|
781
|
+
const { issues, patches } = buildExpressionMutationBatch(prepareInput.mutations, snapshot.model);
|
|
759
782
|
const errors = issues.filter((issue) => issue.severity === "error");
|
|
760
783
|
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
761
784
|
if (errors.length > 0) return reject(rejectedEvent({
|
|
@@ -801,6 +824,104 @@ const surveyExpressionTool = createTool({
|
|
|
801
824
|
toModelOutput: (value) => toTextModelOutput(value, (output) => "action" in output && output.action === "context" ? output.modelSummary : summarizeDraftEvent(output))
|
|
802
825
|
});
|
|
803
826
|
//#endregion
|
|
827
|
+
//#region src/server/tools/localization-inspection.ts
|
|
828
|
+
function describeContent(content, locale, ref) {
|
|
829
|
+
const text = getContentPlainText(content);
|
|
830
|
+
const path = `/translations/${encodeJsonPointerSegment(locale)}/${ref.scope === "content" ? `content/${encodeJsonPointerSegment(ref.ownerId)}` : ref.surface}/${encodeJsonPointerSegment(ref.scope === "content" ? ref.role : ref.contentKey)}`;
|
|
831
|
+
return {
|
|
832
|
+
locale,
|
|
833
|
+
type: content.type,
|
|
834
|
+
text: text.slice(0, 2e3),
|
|
835
|
+
textLength: text.length,
|
|
836
|
+
truncated: text.length > 2e3,
|
|
837
|
+
path,
|
|
838
|
+
...content.type === "richText" ? { content } : { textPath: `${path}/content` }
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function inspectLocalization(input, snapshot) {
|
|
842
|
+
const survey = snapshot.survey;
|
|
843
|
+
const fail = (reason) => ({
|
|
844
|
+
ok: false,
|
|
845
|
+
reason
|
|
846
|
+
});
|
|
847
|
+
if (input.sourceLocale === input.targetLocale) return fail("Source and target locales must differ.");
|
|
848
|
+
if (input.sourceLocale && !ownValue(survey.translations, input.sourceLocale)) return fail(`Source locale "${input.sourceLocale}" is not present in the current survey draft.`);
|
|
849
|
+
const itemId = input.itemRef === "selected" ? snapshot.ui.selectedItemId : input.itemRef;
|
|
850
|
+
if (input.itemRef && (!itemId || !survey.surveyItems.some((item) => item.id === itemId))) return fail("Unknown localization item. Use an existing canonical item id.");
|
|
851
|
+
const targetTranslations = ownValue(survey.translations, input.targetLocale);
|
|
852
|
+
let entries = listLocalizationEntries(snapshot.model, {
|
|
853
|
+
sourceLocale: input.sourceLocale,
|
|
854
|
+
...itemId ? { itemIds: [itemId] } : {}
|
|
855
|
+
}).filter((entry) => Object.entries(entry.contents).some(([locale, content]) => locale !== input.targetLocale && hasLocalizationText(content)));
|
|
856
|
+
const unavailableRefs = [];
|
|
857
|
+
if (input.localizationRefs) {
|
|
858
|
+
const wanted = new Set(input.localizationRefs.map(getLocalizationEntryKey));
|
|
859
|
+
const available = new Set(entries.map((entry) => entry.key));
|
|
860
|
+
for (const ref of input.localizationRefs) if (!available.has(getLocalizationEntryKey(ref))) unavailableRefs.push(ref);
|
|
861
|
+
entries = entries.filter((entry) => wanted.has(entry.key));
|
|
862
|
+
}
|
|
863
|
+
const referenceValues = (entry) => Object.fromEntries(Object.entries(entry.contents).filter(([locale]) => locale !== input.targetLocale));
|
|
864
|
+
const fingerprint = createHash("sha256").update(stableStringify({
|
|
865
|
+
sourceLocale: input.sourceLocale,
|
|
866
|
+
targetLocale: input.targetLocale,
|
|
867
|
+
itemId,
|
|
868
|
+
refs: input.localizationRefs?.map(getLocalizationEntryKey).sort(),
|
|
869
|
+
missingOnly: Boolean(input.missingOnly),
|
|
870
|
+
entries: entries.map((entry) => [entry.key, referenceValues(entry)])
|
|
871
|
+
})).digest("hex");
|
|
872
|
+
let position = 0;
|
|
873
|
+
if (input.cursor) {
|
|
874
|
+
const parsed = /^(\d+)\.([a-f0-9]{64})$/.exec(input.cursor);
|
|
875
|
+
if (!parsed || parsed[2] !== fingerprint || !Number.isSafeInteger(Number(parsed[1])) || Number(parsed[1]) > entries.length) return fail("Localization cursor is stale or invalid. Reference content, order, or scope changed. Restart without a cursor; for review, start a fresh pass and report earlier corrections separately.");
|
|
876
|
+
position = Number(parsed[1]);
|
|
877
|
+
}
|
|
878
|
+
const missingCount = entries.filter((entry) => !hasLocalizationText(ownValue(entry.contents, input.targetLocale))).length;
|
|
879
|
+
const matches = (entry) => !input.missingOnly || !hasLocalizationText(ownValue(entry.contents, input.targetLocale));
|
|
880
|
+
const page = [];
|
|
881
|
+
while (position < entries.length && page.length < (input.limit ?? 20)) {
|
|
882
|
+
const entry = entries[position++];
|
|
883
|
+
if (matches(entry)) page.push(entry);
|
|
884
|
+
}
|
|
885
|
+
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
886
|
+
return {
|
|
887
|
+
ok: true,
|
|
888
|
+
result: {
|
|
889
|
+
...input.sourceLocale ? { sourceLocale: input.sourceLocale } : {},
|
|
890
|
+
targetLocale: input.targetLocale,
|
|
891
|
+
scope: itemId ? {
|
|
892
|
+
itemId,
|
|
893
|
+
includesDescendants: true
|
|
894
|
+
} : { survey: true },
|
|
895
|
+
...input.localizationRefs ? {
|
|
896
|
+
localizationRefs: input.localizationRefs,
|
|
897
|
+
unavailableRefs,
|
|
898
|
+
unavailableReason: "These requested fields no longer exist in scope or lack reference wording in another language. Process the available fields, report unavailable refs separately, and never widen the scope."
|
|
899
|
+
} : {},
|
|
900
|
+
targetLocalePresent: Boolean(targetTranslations),
|
|
901
|
+
missingOnly: Boolean(input.missingOnly),
|
|
902
|
+
totalTexts: entries.length,
|
|
903
|
+
missingCount,
|
|
904
|
+
translatedCount: entries.length - missingCount,
|
|
905
|
+
totalAvailable: input.missingOnly ? missingCount : entries.length,
|
|
906
|
+
returned: page.length,
|
|
907
|
+
...entries.slice(position).some(matches) ? { nextCursor: `${position}.${fingerprint}` } : {},
|
|
908
|
+
contentCoverage: "references contains all available non-target translations, not a single authoritative source. text is a preview. Read truncated plain/md values via raw-json paths: [textPath] and follow nextTextOffset using textOffset until complete. Rich-text content is complete. Read complete references and target before reviewing. targetPresent means presence, not correctness. Ask about material disagreements between references. sourceLocale, when supplied, identifies the explicitly requested source.",
|
|
909
|
+
listOrdering: { surfaces: "Survey texts first, then outline order. Cursor positions are in the complete scoped list before missingOnly. Target-text edits preserve cursors; changes to reference content, query, or entry order require a fresh pass." },
|
|
910
|
+
surfaces: page.map((entry) => ({
|
|
911
|
+
...entry.ref,
|
|
912
|
+
entryKey: entry.key,
|
|
913
|
+
...entry.ref.scope === "content" ? {
|
|
914
|
+
itemId: snapshot.model.owners.get(entry.ref.ownerId)?.itemId,
|
|
915
|
+
itemKey: names.get(snapshot.model.owners.get(entry.ref.ownerId)?.itemId ?? "")
|
|
916
|
+
} : {},
|
|
917
|
+
references: Object.entries(referenceValues(entry)).filter(([, content]) => hasLocalizationText(content)).map(([locale, content]) => describeContent(content, locale, entry.ref)),
|
|
918
|
+
targetPresent: hasLocalizationText(ownValue(entry.contents, input.targetLocale)),
|
|
919
|
+
...ownValue(entry.contents, input.targetLocale) ? { target: describeContent(ownValue(entry.contents, input.targetLocale), input.targetLocale, entry.ref) } : {}
|
|
920
|
+
}))
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
//#endregion
|
|
804
925
|
//#region src/server/tools/parse-inspection-output.ts
|
|
805
926
|
const parseInspectionOutput = (schema, value, backend) => {
|
|
806
927
|
const parsed = schema.safeParse(value);
|
|
@@ -815,7 +936,7 @@ const responseVariableSummarySchema = z.object({
|
|
|
815
936
|
itemId: z.string(),
|
|
816
937
|
variableName: z.string(),
|
|
817
938
|
valueType: z.string(),
|
|
818
|
-
|
|
939
|
+
elementId: z.string().optional(),
|
|
819
940
|
rowId: z.string().optional(),
|
|
820
941
|
columnId: z.string().optional(),
|
|
821
942
|
namingKind: z.enum(["variable", "matrix"]).optional(),
|
|
@@ -824,8 +945,8 @@ const responseVariableSummarySchema = z.object({
|
|
|
824
945
|
sourceSlotIds: z.array(z.string())
|
|
825
946
|
}).optional(),
|
|
826
947
|
computedSourcesTruncated: z.boolean(),
|
|
827
|
-
referenceValueKeys:
|
|
828
|
-
referenceValueScores:
|
|
948
|
+
referenceValueKeys: stringRecord(z.string(), z.string()).optional(),
|
|
949
|
+
referenceValueScores: stringRecord(z.string(), z.number()).optional(),
|
|
829
950
|
optionIds: z.array(z.string()),
|
|
830
951
|
optionsTotal: z.number(),
|
|
831
952
|
optionsTruncated: z.boolean()
|
|
@@ -844,7 +965,7 @@ const summarizeResponseVariable = (slot) => {
|
|
|
844
965
|
itemId: slot.itemId,
|
|
845
966
|
variableName: slot.variableName,
|
|
846
967
|
valueType: slot.valueType,
|
|
847
|
-
|
|
968
|
+
elementId: slot.elementId,
|
|
848
969
|
rowId: slot.rowId,
|
|
849
970
|
columnId: slot.columnId,
|
|
850
971
|
namingKind: slot.naming?.kind,
|
|
@@ -864,7 +985,7 @@ const responseVariableMatchesQuery = (slot, query) => [
|
|
|
864
985
|
slot.slotId,
|
|
865
986
|
slot.itemId,
|
|
866
987
|
slot.variableName,
|
|
867
|
-
slot.
|
|
988
|
+
slot.elementId,
|
|
868
989
|
slot.rowId,
|
|
869
990
|
slot.columnId,
|
|
870
991
|
...responseOptionIds(slot),
|
|
@@ -887,7 +1008,8 @@ const outlineModeSchema$1 = z.enum([
|
|
|
887
1008
|
]);
|
|
888
1009
|
const translationSnippetSchema = z.object({
|
|
889
1010
|
locale: z.string(),
|
|
890
|
-
|
|
1011
|
+
ownerId: z.string(),
|
|
1012
|
+
role: z.string(),
|
|
891
1013
|
text: z.string()
|
|
892
1014
|
});
|
|
893
1015
|
const inputSchema$7 = z.object({
|
|
@@ -904,7 +1026,7 @@ const outputSchema$6 = z.object({
|
|
|
904
1026
|
revision: z.number().int().nonnegative().optional(),
|
|
905
1027
|
contentHash: z.string().optional(),
|
|
906
1028
|
scopes: z.array(contextScopeSchema).optional(),
|
|
907
|
-
listOrdering:
|
|
1029
|
+
listOrdering: stringRecord(z.string(), z.string()).optional().describe("Meaning of each returned list. A list is positional only when its entry says so."),
|
|
908
1030
|
summary: z.object({
|
|
909
1031
|
sessionId: z.string().nullable(),
|
|
910
1032
|
view: z.string(),
|
|
@@ -920,9 +1042,9 @@ const outputSchema$6 = z.object({
|
|
|
920
1042
|
outlineItemCount: z.number().int().nonnegative(),
|
|
921
1043
|
responseSlotCount: z.number().int().nonnegative(),
|
|
922
1044
|
rootItem: itemSummarySchema.optional(),
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1045
|
+
availableElementTypes: z.array(z.string()),
|
|
1046
|
+
deterministicCreateElementTypes: z.array(z.string()),
|
|
1047
|
+
elementTypeDetails: z.array(assistantElementTypeManifestSchema)
|
|
926
1048
|
}).optional(),
|
|
927
1049
|
selectedItem: z.object({
|
|
928
1050
|
available: z.boolean(),
|
|
@@ -971,7 +1093,13 @@ const outputSchema$6 = z.object({
|
|
|
971
1093
|
componentId: z.string().optional(),
|
|
972
1094
|
label: z.string().optional(),
|
|
973
1095
|
details: z.string().optional()
|
|
974
|
-
}))
|
|
1096
|
+
})),
|
|
1097
|
+
task: z.object({
|
|
1098
|
+
kind: z.string(),
|
|
1099
|
+
title: z.string().optional(),
|
|
1100
|
+
instructions: z.string().optional(),
|
|
1101
|
+
data: z.unknown().optional()
|
|
1102
|
+
}).optional()
|
|
975
1103
|
}).optional(),
|
|
976
1104
|
history: z.object({
|
|
977
1105
|
currentIndex: z.number().int(),
|
|
@@ -1030,9 +1158,9 @@ const buildSummary = (snapshot) => ({
|
|
|
1030
1158
|
historyIndex: snapshot.version.historyIndex,
|
|
1031
1159
|
outlineItemCount: snapshot.inspection.itemIndex.length,
|
|
1032
1160
|
responseSlotCount: snapshot.inspection.responseSlots.length,
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1161
|
+
availableElementTypes: snapshot.capabilities.elementTypes.map((entry) => entry.elementType),
|
|
1162
|
+
deterministicCreateElementTypes: getDeterministicCreateElementTypes(snapshot.capabilities),
|
|
1163
|
+
elementTypeDetails: snapshot.capabilities.elementTypes
|
|
1036
1164
|
});
|
|
1037
1165
|
const buildSelectedItem = (snapshot, input) => {
|
|
1038
1166
|
const outlineById = mapOutlineById(snapshot);
|
|
@@ -1087,7 +1215,7 @@ const buildOutline = (snapshot, input) => {
|
|
|
1087
1215
|
if (!parent) note = "The requested item could not be resolved by exact item ID.";
|
|
1088
1216
|
else {
|
|
1089
1217
|
const rawParent = snapshot.survey.surveyItems.find((item) => item.id === parent.itemId);
|
|
1090
|
-
if (rawParent?.itemType === "group") parentShuffled =
|
|
1218
|
+
if (rawParent?.itemType === "group") parentShuffled = rawParent.config.shuffleItems === true;
|
|
1091
1219
|
}
|
|
1092
1220
|
} else if (outlineMode === "focused" && requestedItemId && snapshot.inspection.focus?.focusItemId === requestedItemId) {
|
|
1093
1221
|
const focus = snapshot.inspection.focus;
|
|
@@ -1161,9 +1289,9 @@ const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
|
1161
1289
|
if (scopes.includes("summary")) Object.assign(listOrdering, {
|
|
1162
1290
|
"summary.recentItemIds": "Most-recent-first UI history; not survey structure.",
|
|
1163
1291
|
"summary.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1164
|
-
"summary.
|
|
1165
|
-
"summary.
|
|
1166
|
-
"summary.
|
|
1292
|
+
"summary.availableElementTypes": "Capability registry order; treat as a set of available types.",
|
|
1293
|
+
"summary.deterministicCreateElementTypes": "Capability registry order; treat as a set of available types.",
|
|
1294
|
+
"summary.elementTypeDetails": "Capability registry order; treat as a set of available type definitions."
|
|
1167
1295
|
});
|
|
1168
1296
|
if (scopes.includes("selected-item")) Object.assign(listOrdering, {
|
|
1169
1297
|
"selectedItem.pathItems": "Root-to-selected survey-tree path.",
|
|
@@ -1191,7 +1319,8 @@ const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
|
1191
1319
|
if (scopes.includes("ui-context")) output.uiContext = {
|
|
1192
1320
|
activeSurface: snapshot.ui.activeSurface,
|
|
1193
1321
|
openPanels: snapshot.ui.openPanels ?? [],
|
|
1194
|
-
highlights: snapshot.ui.highlights ?? []
|
|
1322
|
+
highlights: snapshot.ui.highlights ?? [],
|
|
1323
|
+
...snapshot.ui.task ? { task: snapshot.ui.task } : {}
|
|
1195
1324
|
};
|
|
1196
1325
|
if (scopes.includes("history") && snapshot.ui.history) output.history = snapshot.ui.history;
|
|
1197
1326
|
return parseInspectionOutput(outputSchema$6, output, "Current-context inspection");
|
|
@@ -1204,9 +1333,8 @@ const inputSchema$6 = z.object({
|
|
|
1204
1333
|
itemId: z.string().describe("Exact persistent itemId. Discover candidates by editor name, breadcrumb or content using survey_inspect search; duplicate names are allowed, so select the intended ID."),
|
|
1205
1334
|
locales: z.array(z.string()).optional().describe("Optional locale codes to restrict returned translations to. Defaults to all locales."),
|
|
1206
1335
|
textLimit: z.number().int().min(20).max(2e3).default(400).describe("Maximum characters returned per translation snippet."),
|
|
1207
|
-
includeChildren: z.boolean().optional().describe("Deprecated alias for includeSurveyChildren. This means survey-tree children only, not choice options or form fields."),
|
|
1208
1336
|
includeSurveyChildren: z.boolean().default(false).describe("Whether to include a shallow list of this item's direct survey-tree children, such as group members."),
|
|
1209
|
-
includeItemConfig: z.boolean().default(true).describe("Whether to include item-internal config details
|
|
1337
|
+
includeItemConfig: z.boolean().default(true).describe("Whether to include item-internal config details including elements, owned components and follow-up bodies."),
|
|
1210
1338
|
childrenLimit: z.number().int().min(1).max(50).default(20).describe("Maximum number of direct survey-tree children to return.")
|
|
1211
1339
|
});
|
|
1212
1340
|
const outputSchema$5 = z.object({
|
|
@@ -1216,23 +1344,22 @@ const outputSchema$5 = z.object({
|
|
|
1216
1344
|
translations: z.array(translationEntrySchema).optional(),
|
|
1217
1345
|
surveyChildren: z.array(itemSummarySchema).optional(),
|
|
1218
1346
|
surveyChildrenTruncated: z.boolean().optional(),
|
|
1219
|
-
listOrdering:
|
|
1347
|
+
listOrdering: stringRecord(z.string(), z.string()).optional().describe("Meaning of each returned list. A list is positional only when its entry says so."),
|
|
1220
1348
|
configDetails: itemConfigDetailsSchema.optional(),
|
|
1221
1349
|
responseVariables: z.array(responseVariableSummarySchema).optional(),
|
|
1222
1350
|
responseVariablesTotal: z.number().optional(),
|
|
1223
1351
|
responseVariablesTruncated: z.boolean().optional(),
|
|
1224
1352
|
responseVariablesNextCursor: z.string().optional(),
|
|
1225
1353
|
responseVariablesNote: z.string().optional(),
|
|
1226
|
-
capabilityDetails: z.object({
|
|
1354
|
+
capabilityDetails: z.array(z.object({
|
|
1355
|
+
elementId: z.string(),
|
|
1356
|
+
elementType: z.string(),
|
|
1227
1357
|
summary: z.string(),
|
|
1228
1358
|
details: z.unknown().optional()
|
|
1229
|
-
}).optional()
|
|
1359
|
+
})).optional()
|
|
1230
1360
|
});
|
|
1231
1361
|
const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
1232
|
-
const input = inputSchema$6.parse(
|
|
1233
|
-
...inputData,
|
|
1234
|
-
includeSurveyChildren: inputData.includeSurveyChildren ?? inputData.includeChildren
|
|
1235
|
-
});
|
|
1362
|
+
const input = inputSchema$6.parse(inputData);
|
|
1236
1363
|
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1237
1364
|
if (!snapshot) return {
|
|
1238
1365
|
ok: false,
|
|
@@ -1247,14 +1374,13 @@ const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
|
1247
1374
|
reason: `No item matching "${input.itemId}" (by exact itemId) was found in the current context snapshot.`
|
|
1248
1375
|
};
|
|
1249
1376
|
const rawItem = rawItemsById.get(item.itemId);
|
|
1250
|
-
const rawConfig = rawItem?.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1251
1377
|
const output = {
|
|
1252
1378
|
ok: true,
|
|
1253
1379
|
item,
|
|
1254
1380
|
translations: buildItemTranslationEntries(snapshot, item.itemId, input),
|
|
1255
1381
|
listOrdering: {
|
|
1256
|
-
translations: "Grouped by locale and then
|
|
1257
|
-
surveyChildren:
|
|
1382
|
+
translations: "Grouped by locale and then declared content-owner and role order; this list is not positional survey content.",
|
|
1383
|
+
surveyChildren: rawItem?.itemType === "group" && rawItem.config.shuffleItems === true ? "Stored group-child order; respondents see shuffled children, so this is not a stable respondent-visible order." : "Stored group-child order; this is the respondent-visible order when the parent is a group."
|
|
1258
1384
|
}
|
|
1259
1385
|
};
|
|
1260
1386
|
if (input.includeSurveyChildren) {
|
|
@@ -1274,22 +1400,19 @@ const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
|
1274
1400
|
output.responseVariablesTotal = variables.length;
|
|
1275
1401
|
output.responseVariablesTruncated = page.truncated;
|
|
1276
1402
|
output.responseVariablesNextCursor = page.nextCursor;
|
|
1277
|
-
output.responseVariablesNote = "Use responseVariablesNextCursor as cursor with the same
|
|
1278
|
-
output.configDetails = rawItem ? buildItemConfigDetails(rawItem) : void 0;
|
|
1279
|
-
output.capabilityDetails =
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
if (embeddedFormDetails?.formFieldGroups?.length) output.configDetails.formFieldGroups = [...output.configDetails.formFieldGroups ?? [], ...embeddedFormDetails.formFieldGroups];
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1403
|
+
output.responseVariablesNote = "Use responseVariablesNextCursor as cursor with the same itemId to continue. Option and computed-source summaries include up to 50 entries; inspect exact raw-json config paths for complete domains and export settings. Restart pagination after mutations.";
|
|
1404
|
+
output.configDetails = rawItem ? buildItemConfigDetails(rawItem, snapshot.model) : void 0;
|
|
1405
|
+
output.capabilityDetails = [...snapshot.model.elements.values()].filter((element) => element.itemId === item.itemId).flatMap(({ node }) => {
|
|
1406
|
+
const details = inspectAssistantElement({
|
|
1407
|
+
capabilities: snapshot.trustedCapabilities,
|
|
1408
|
+
element: node
|
|
1409
|
+
});
|
|
1410
|
+
return details ? [{
|
|
1411
|
+
elementId: node.id,
|
|
1412
|
+
elementType: node.elementType,
|
|
1413
|
+
...details
|
|
1414
|
+
}] : [];
|
|
1415
|
+
});
|
|
1293
1416
|
}
|
|
1294
1417
|
return parseInspectionOutput(outputSchema$5, output, "Item-details inspection");
|
|
1295
1418
|
};
|
|
@@ -1297,7 +1420,7 @@ const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
|
1297
1420
|
//#region src/server/tools/inspect-raw-survey.ts
|
|
1298
1421
|
const DEFAULT_VALUE_CHARACTER_LIMIT = 12e3;
|
|
1299
1422
|
const MAX_VALUE_CHARACTER_LIMIT = 6e4;
|
|
1300
|
-
const jsonPointerPathSchema = z.string().max(500).describe("JSON Pointer path into the survey document. Examples: \"/surveyItems/0/config/items\", \"/metadata\", \"/translations/en/
|
|
1423
|
+
const jsonPointerPathSchema = z.string().max(500).describe("JSON Pointer path into the survey document. Examples: \"/surveyItems/0/config/items\", \"/metadata\", \"/translations/en/content/<ownerId>/title\".");
|
|
1301
1424
|
const inputSchema$5 = z.object({
|
|
1302
1425
|
paths: z.array(jsonPointerPathSchema).min(1).max(20).optional().describe("Optional exact JSON Pointer paths to inspect. Omit paths and itemIdentifiers for a top-level raw survey overview."),
|
|
1303
1426
|
itemIdentifiers: z.array(z.string().min(1).max(240)).min(1).max(20).optional().describe("Optional exact item IDs to inspect. Returns exact /surveyItems/<index> paths plus item translation paths for each item."),
|
|
@@ -1317,7 +1440,7 @@ const outputSchema$4 = z.object({
|
|
|
1317
1440
|
reason: z.string().optional(),
|
|
1318
1441
|
revision: z.number().int().nonnegative().optional(),
|
|
1319
1442
|
contentHash: z.string().optional(),
|
|
1320
|
-
listOrdering:
|
|
1443
|
+
listOrdering: stringRecord(z.string(), z.string()).optional().describe("Meaning of each returned list. Raw JSON arrays retain their stored order."),
|
|
1321
1444
|
overview: z.object({
|
|
1322
1445
|
topLevelKeys: z.array(z.string()),
|
|
1323
1446
|
surveyItemCount: z.number().int().nonnegative(),
|
|
@@ -1347,7 +1470,7 @@ const outputSchema$4 = z.object({
|
|
|
1347
1470
|
rawItemPreview: z.string().optional(),
|
|
1348
1471
|
rawItemTruncated: z.boolean().optional(),
|
|
1349
1472
|
translationPaths: z.array(z.string()).optional(),
|
|
1350
|
-
translationsByLocale:
|
|
1473
|
+
translationsByLocale: stringRecord(z.string(), z.unknown()).optional(),
|
|
1351
1474
|
translationsPreview: z.string().optional(),
|
|
1352
1475
|
translationsTruncated: z.boolean().optional(),
|
|
1353
1476
|
error: z.string().optional()
|
|
@@ -1446,16 +1569,19 @@ const findRawItemByIdentifier = (snapshot, identifier) => {
|
|
|
1446
1569
|
index: resolvedIndex
|
|
1447
1570
|
};
|
|
1448
1571
|
};
|
|
1449
|
-
const getTranslationsByLocaleForItem = (
|
|
1572
|
+
const getTranslationsByLocaleForItem = (snapshot, itemId) => {
|
|
1450
1573
|
const entries = {};
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1574
|
+
const ownerIds = [...snapshot.model.owners.values()].filter((owner) => owner.itemId === itemId).map((owner) => owner.id);
|
|
1575
|
+
for (const [locale, translations] of Object.entries(snapshot.survey.translations ?? {})) {
|
|
1576
|
+
const content = Object.fromEntries(ownerIds.flatMap((ownerId) => {
|
|
1577
|
+
const content = ownValue(translations.content, ownerId);
|
|
1578
|
+
return content ? [[ownerId, content]] : [];
|
|
1579
|
+
}));
|
|
1580
|
+
if (Object.keys(content).length) setOwnValue(entries, locale, content);
|
|
1454
1581
|
}
|
|
1455
1582
|
return entries;
|
|
1456
1583
|
};
|
|
1457
1584
|
const buildItemValues = (snapshot, input) => {
|
|
1458
|
-
const rawSurvey = getRawSurvey(snapshot);
|
|
1459
1585
|
return input.itemIdentifiers.map((identifier) => {
|
|
1460
1586
|
const resolved = findRawItemByIdentifier(snapshot, identifier);
|
|
1461
1587
|
if (!resolved) return {
|
|
@@ -1465,7 +1591,7 @@ const buildItemValues = (snapshot, input) => {
|
|
|
1465
1591
|
};
|
|
1466
1592
|
const itemId = resolved.item.id;
|
|
1467
1593
|
const rawItemOutput = toOutputValue(resolved.item, input.valueCharacterLimit);
|
|
1468
|
-
const translationsByLocale = getTranslationsByLocaleForItem(
|
|
1594
|
+
const translationsByLocale = getTranslationsByLocaleForItem(snapshot, itemId);
|
|
1469
1595
|
const translationsOutput = toOutputValue(translationsByLocale, input.valueCharacterLimit);
|
|
1470
1596
|
const translationsValue = "value" in translationsOutput && isRecord$1(translationsOutput.value) ? translationsOutput.value : void 0;
|
|
1471
1597
|
return {
|
|
@@ -1476,7 +1602,7 @@ const buildItemValues = (snapshot, input) => {
|
|
|
1476
1602
|
rawItem: "value" in rawItemOutput ? rawItemOutput.value : void 0,
|
|
1477
1603
|
rawItemPreview: "preview" in rawItemOutput ? rawItemOutput.preview : void 0,
|
|
1478
1604
|
rawItemTruncated: rawItemOutput.truncated,
|
|
1479
|
-
translationPaths: snapshot.inspection.locales.
|
|
1605
|
+
translationPaths: snapshot.inspection.locales.flatMap((locale) => [...snapshot.model.owners.values()].filter((owner) => owner.itemId === itemId).map((owner) => `/translations/${encodeJsonPointerSegment(locale)}/content/${encodeJsonPointerSegment(owner.id)}`)),
|
|
1480
1606
|
translationsByLocale: translationsValue,
|
|
1481
1607
|
translationsPreview: "preview" in translationsOutput ? translationsOutput.preview : void 0,
|
|
1482
1608
|
translationsTruncated: translationsOutput.truncated
|
|
@@ -1526,7 +1652,8 @@ const searchMatchSchema = z.object({
|
|
|
1526
1652
|
]),
|
|
1527
1653
|
field: z.string().optional(),
|
|
1528
1654
|
locale: z.string().optional(),
|
|
1529
|
-
|
|
1655
|
+
ownerId: z.string().optional(),
|
|
1656
|
+
role: z.string().optional(),
|
|
1530
1657
|
text: z.string(),
|
|
1531
1658
|
score: z.number()
|
|
1532
1659
|
});
|
|
@@ -1537,7 +1664,8 @@ const searchResultSchema = z.object({
|
|
|
1537
1664
|
});
|
|
1538
1665
|
const inputSchema$4 = z.object({
|
|
1539
1666
|
query: z.string().min(1).max(MAX_QUERY_LENGTH).describe("Text to search for across all survey items, including visible translated content such as titles, option labels, and form field labels. Examples: \"age\", \"consent\", \"blood pressure\"."),
|
|
1540
|
-
itemTypes: z.array(z.string()).optional().describe("Optional item types to restrict results to,
|
|
1667
|
+
itemTypes: z.array(z.string()).optional().describe("Optional item types to restrict results to, content, group or page-break."),
|
|
1668
|
+
elementTypes: z.array(z.string()).optional().describe("Optional registered element types. Return items containing at least one matching element, including option follow-ups."),
|
|
1541
1669
|
locales: z.array(z.string()).optional().describe("Optional locale codes to restrict translation-content matches to."),
|
|
1542
1670
|
cursor: z.string().optional().describe("Optional numeric cursor returned by a previous search."),
|
|
1543
1671
|
limit: z.number().int().min(1).max(50).default(12).describe("Maximum number of matching items to return.")
|
|
@@ -1596,7 +1724,7 @@ const scoreText = (value, query, terms, baseScore) => {
|
|
|
1596
1724
|
const addMatch = (matches, match) => {
|
|
1597
1725
|
const text = match.text?.trim();
|
|
1598
1726
|
if (!text) return;
|
|
1599
|
-
if (!matches.some((existing) => existing.source === match.source && existing.field === match.field && existing.locale === match.locale && existing.
|
|
1727
|
+
if (!matches.some((existing) => existing.source === match.source && existing.field === match.field && existing.locale === match.locale && existing.ownerId === match.ownerId && existing.role === match.role && existing.text === text)) matches.push({
|
|
1600
1728
|
...match,
|
|
1601
1729
|
text
|
|
1602
1730
|
});
|
|
@@ -1625,11 +1753,19 @@ const candidateItems = (snapshot) => {
|
|
|
1625
1753
|
const searchItems = (snapshot, input) => {
|
|
1626
1754
|
const terms = queryTerms(input.query);
|
|
1627
1755
|
const itemTypeFilter = input.itemTypes?.length ? new Set(input.itemTypes) : void 0;
|
|
1628
|
-
|
|
1756
|
+
const elementTypeFilter = input.elementTypes?.length ? new Set(input.elementTypes) : void 0;
|
|
1757
|
+
const typesByItem = /* @__PURE__ */ new Map();
|
|
1758
|
+
for (const element of snapshot.model.elements.values()) {
|
|
1759
|
+
const types = typesByItem.get(element.itemId) ?? /* @__PURE__ */ new Set();
|
|
1760
|
+
types.add(element.node.elementType);
|
|
1761
|
+
typesByItem.set(element.itemId, types);
|
|
1762
|
+
}
|
|
1763
|
+
return candidateItems(snapshot).filter(({ item }) => !elementTypeFilter || [...typesByItem.get(item.itemId) ?? []].some((type) => elementTypeFilter.has(type))).filter(({ item }) => !itemTypeFilter || itemTypeFilter.has(item.itemType)).map(({ item, rawItem }) => {
|
|
1629
1764
|
const matches = [];
|
|
1630
1765
|
const structuralFields = [
|
|
1631
1766
|
["itemId", item.itemId],
|
|
1632
1767
|
["itemType", item.itemType],
|
|
1768
|
+
["elementTypes", [...typesByItem.get(item.itemId) ?? []].join(" ")],
|
|
1633
1769
|
["key", item.editorName],
|
|
1634
1770
|
["breadcrumb", item.breadcrumb],
|
|
1635
1771
|
["itemLabel", item.itemLabel],
|
|
@@ -1649,16 +1785,17 @@ const searchItems = (snapshot, input) => {
|
|
|
1649
1785
|
locales: input.locales,
|
|
1650
1786
|
textLimit: DEFAULT_TEXT_LIMIT
|
|
1651
1787
|
})) {
|
|
1652
|
-
const score = scoreText(`${translation.
|
|
1788
|
+
const score = scoreText(`${translation.ownerId} ${translation.role} ${translation.text}`, input.query, terms, 100);
|
|
1653
1789
|
if (score !== null) addMatch(matches, {
|
|
1654
1790
|
source: "translation",
|
|
1655
1791
|
locale: translation.locale,
|
|
1656
|
-
|
|
1792
|
+
ownerId: translation.ownerId,
|
|
1793
|
+
role: translation.role,
|
|
1657
1794
|
text: translation.text,
|
|
1658
1795
|
score
|
|
1659
1796
|
});
|
|
1660
1797
|
}
|
|
1661
|
-
if (rawItem) for (const text of buildConfigSearchText(rawItem)) {
|
|
1798
|
+
if (rawItem) for (const text of buildConfigSearchText(rawItem, snapshot.model)) {
|
|
1662
1799
|
const score = scoreText(text, input.query, terms, 60);
|
|
1663
1800
|
if (score !== null) addMatch(matches, {
|
|
1664
1801
|
source: "config",
|
|
@@ -1730,11 +1867,27 @@ const inputSchema$3 = z.object({
|
|
|
1730
1867
|
"history"
|
|
1731
1868
|
])).min(1).max(6)).describe("For current-context: context sections to inspect. Empty means omitted."),
|
|
1732
1869
|
query: optionalNonBlankString(180).describe("For search-items: text to search across survey content. For assets: optional asset id, filename, name, ALT text, or media type filter. For current-context outline: optional editor-name/breadcrumb/label filter."),
|
|
1733
|
-
|
|
1870
|
+
itemTypes: optionalArrayInput(z.array(z.enum([
|
|
1871
|
+
"content",
|
|
1872
|
+
"group",
|
|
1873
|
+
"page-break"
|
|
1874
|
+
]))).describe("For search-items: optional flow item types."),
|
|
1875
|
+
elementTypes: optionalArrayInput(z.array(z.string().min(1).max(120))).describe("For search-items: optional registered element types contained in matching items."),
|
|
1876
|
+
locales: optionalArrayInput(z.array(z.string().min(1).max(40))).describe("For search-items: optional languages for translation matches."),
|
|
1877
|
+
sourceLocale: optionalNonBlankString(40).describe("For localization: optional explicitly requested source locale. Omit to use all available language versions as references; the selected UI locale does not establish a source."),
|
|
1734
1878
|
targetLocale: optionalNonBlankString(40).describe("For localization: required target locale whose coverage should be inspected."),
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1879
|
+
localizationRefs: optionalArrayInput(z.array(z.discriminatedUnion("scope", [z.object({
|
|
1880
|
+
scope: z.literal("content"),
|
|
1881
|
+
ownerId: z.string().min(1).max(240),
|
|
1882
|
+
role: z.string().min(1).max(500)
|
|
1883
|
+
}), z.object({
|
|
1884
|
+
scope: z.literal("survey"),
|
|
1885
|
+
surface: z.enum(SURVEY_LOCALIZATION_SURFACES$1),
|
|
1886
|
+
contentKey: z.string().min(1).max(500)
|
|
1887
|
+
})])).min(1).max(200)).describe("For localization: optional exact text field references selected by the author. Process larger selections in groups of at most 200 references."),
|
|
1888
|
+
missingOnly: optionalInput(z.boolean()).describe("For localization: return only texts missing in the target locale and present in at least one reference language."),
|
|
1889
|
+
itemRef: optionalNonBlankString(240).describe("For localization: an existing canonical item id or \"selected\"; scopes to its subtree, excluding survey texts. For current-context outline, item-details, or raw-json: item reference."),
|
|
1890
|
+
cursor: optionalNonBlankString(240).describe("Use the exact cursor returned by the previous result. Localization uses position.fingerprint, stable across target-text edits. Other actions use numeric offsets."),
|
|
1738
1891
|
outlineMode: optionalInput(outlineModeSchema).describe("For current-context outline: \"full\" pages all survey items, \"focused\" returns the selected/focused neighborhood, and \"children\" returns direct children of itemRef in stored sibling order. To verify placement or prepare a reorder, use \"children\" with the parent itemRef and page until complete; shuffled groups may render a different respondent order."),
|
|
1739
1892
|
paths: optionalArrayInput(z.array(z.string().max(500)).min(1).max(MAX_RAW_JSON_PATHS_PER_REQUEST)).describe(`For raw-json: exact JSON Pointer paths to inspect in request order. itemRef takes precedence when both are supplied. Up to ${MAX_RAW_JSON_PATHS_PER_RESULT} paths are returned per call; larger valid requests receive explicit coverage and retry guidance instead of failing input validation. An empty paths array means omitted; a path of "" selects the document root (JSON Pointer), while "/" selects an empty property name.`),
|
|
1740
1893
|
textOffset: optionalInput(z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)).describe("For raw-json exact string paths: nextTextOffset from a partial result. Continue with the same single path until nextTextOffset is absent; restart after mutations."),
|
|
@@ -1775,149 +1928,27 @@ const inspectionBackendFailure = (action, reason) => ({
|
|
|
1775
1928
|
modelSummary: reason
|
|
1776
1929
|
});
|
|
1777
1930
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1778
|
-
const MAX_LOCALIZATION_TEXT_CHARS = 2e3;
|
|
1779
|
-
const SURVEY_TRANSLATION_SURFACES = [
|
|
1780
|
-
"surveyCardContent",
|
|
1781
|
-
"navigationContent",
|
|
1782
|
-
"validationMessages"
|
|
1783
|
-
];
|
|
1784
|
-
const truncateLocalizationText = (content) => {
|
|
1785
|
-
const text = getContentPlainText(content).trim();
|
|
1786
|
-
return text.length > MAX_LOCALIZATION_TEXT_CHARS ? `${text.slice(0, MAX_LOCALIZATION_TEXT_CHARS)}...` : text;
|
|
1787
|
-
};
|
|
1788
|
-
const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
1789
|
-
const survey = snapshot.survey;
|
|
1790
|
-
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
1791
|
-
const sourceLocale = input.sourceLocale ?? snapshot.ui.selectedLocale;
|
|
1792
|
-
const sourceTranslations = survey.translations?.[sourceLocale];
|
|
1793
|
-
const targetTranslations = survey.translations?.[targetLocale];
|
|
1794
|
-
if (!sourceTranslations) return {
|
|
1795
|
-
ok: false,
|
|
1796
|
-
reason: `Source locale "${sourceLocale}" is not present in the current survey draft.`
|
|
1797
|
-
};
|
|
1798
|
-
const surfaces = [];
|
|
1799
|
-
const addSurface = ({ contentKey, itemId, itemKey, itemType, sourceContent, surface, targetContent }) => {
|
|
1800
|
-
const sourceText = truncateLocalizationText(sourceContent);
|
|
1801
|
-
if (!sourceText) return;
|
|
1802
|
-
const sourcePath = `/translations/${encodeJsonPointerSegment(sourceLocale)}/${itemId ? `itemTranslations/${encodeJsonPointerSegment(itemId)}` : surface}/${encodeJsonPointerSegment(contentKey)}`;
|
|
1803
|
-
surfaces.push({
|
|
1804
|
-
scope: itemId ? "item" : "survey",
|
|
1805
|
-
...surface ? { surface } : {},
|
|
1806
|
-
...itemId ? {
|
|
1807
|
-
itemId,
|
|
1808
|
-
itemKey,
|
|
1809
|
-
itemType
|
|
1810
|
-
} : {},
|
|
1811
|
-
contentKey,
|
|
1812
|
-
sourceType: sourceContent.type,
|
|
1813
|
-
sourceText,
|
|
1814
|
-
sourceTextLength: getContentPlainText(sourceContent).trim().length,
|
|
1815
|
-
sourceTruncated: getContentPlainText(sourceContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS,
|
|
1816
|
-
sourcePath,
|
|
1817
|
-
...sourceContent.type !== "richText" ? { sourceTextPath: `${sourcePath}/content` } : {},
|
|
1818
|
-
...sourceContent.type === "richText" ? { sourceContent } : {},
|
|
1819
|
-
targetPresent: Boolean(targetContent && truncateLocalizationText(targetContent)),
|
|
1820
|
-
...targetContent ? {
|
|
1821
|
-
targetType: targetContent.type,
|
|
1822
|
-
targetText: truncateLocalizationText(targetContent),
|
|
1823
|
-
targetTextLength: getContentPlainText(targetContent).trim().length,
|
|
1824
|
-
targetTruncated: getContentPlainText(targetContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS
|
|
1825
|
-
} : {}
|
|
1826
|
-
});
|
|
1827
|
-
};
|
|
1828
|
-
for (const surface of SURVEY_TRANSLATION_SURFACES) {
|
|
1829
|
-
const targetSurface = targetTranslations?.[surface];
|
|
1830
|
-
for (const [contentKey, sourceContent] of Object.entries(sourceTranslations[surface] ?? {})) addSurface({
|
|
1831
|
-
surface,
|
|
1832
|
-
contentKey,
|
|
1833
|
-
sourceContent,
|
|
1834
|
-
targetContent: targetSurface?.[contentKey]
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
for (const item of survey.surveyItems) for (const [contentKey, sourceContent] of Object.entries(sourceTranslations.itemTranslations?.[item.id] ?? {})) addSurface({
|
|
1838
|
-
itemId: item.id,
|
|
1839
|
-
itemKey: names.get(item.id) ?? item.id,
|
|
1840
|
-
itemType: item.itemType,
|
|
1841
|
-
contentKey,
|
|
1842
|
-
sourceContent,
|
|
1843
|
-
targetContent: targetTranslations?.itemTranslations?.[item.id]?.[contentKey]
|
|
1844
|
-
});
|
|
1845
|
-
const missingCount = surfaces.filter((surface) => !surface.targetPresent).length;
|
|
1846
|
-
const filteredSurfaces = input.missingOnly ? surfaces.filter((surface) => !surface.targetPresent) : surfaces;
|
|
1847
|
-
const parsedCursor = Number.parseInt(input.cursor ?? "0", 10);
|
|
1848
|
-
const offset = Number.isFinite(parsedCursor) && parsedCursor >= 0 ? parsedCursor : 0;
|
|
1849
|
-
const limit = input.limit ?? 20;
|
|
1850
|
-
const page = filteredSurfaces.slice(offset, offset + limit);
|
|
1851
|
-
const nextOffset = offset + page.length;
|
|
1852
|
-
return {
|
|
1853
|
-
ok: true,
|
|
1854
|
-
result: {
|
|
1855
|
-
sourceLocale,
|
|
1856
|
-
targetLocale,
|
|
1857
|
-
missingOnly: input.missingOnly ?? false,
|
|
1858
|
-
contentCoverage: "sourceText and targetText are previews. For sourceTruncated plain text, inspect raw-json paths: [sourceTextPath] and follow nextTextOffset with textOffset until complete before translating. Rich-text sourceContent is the complete canonical value. Never translate only a truncated preview or treat targetPresent as proof of translation completeness.",
|
|
1859
|
-
totalSourceSurfaces: surfaces.length,
|
|
1860
|
-
totalAvailable: filteredSurfaces.length,
|
|
1861
|
-
missingCount,
|
|
1862
|
-
returned: page.length,
|
|
1863
|
-
...nextOffset < filteredSurfaces.length ? { nextCursor: String(nextOffset) } : {},
|
|
1864
|
-
listOrdering: { surfaces: "Survey-level translation surfaces first, then item translations in stored survey-item and source content-key order. Cursor pagination preserves this order only while the draft is unchanged; restart without a cursor after mutations." },
|
|
1865
|
-
surfaces: page
|
|
1866
|
-
}
|
|
1867
|
-
};
|
|
1868
|
-
};
|
|
1869
1931
|
const resolveItemRef = (input, snapshot) => {
|
|
1870
1932
|
if (input.itemRef && input.itemRef !== "selected") return input.itemRef;
|
|
1871
1933
|
return snapshot?.ui.selectedItemId;
|
|
1872
1934
|
};
|
|
1873
|
-
const listAssetUsageSummaries = (survey) => {
|
|
1874
|
-
const usagesByAssetId = /* @__PURE__ */ new Map();
|
|
1875
|
-
const addContent = (content, usage) => {
|
|
1876
|
-
for (const assetUsage of getAssetUsagesFromContent(content)) {
|
|
1877
|
-
const usages = usagesByAssetId.get(assetUsage.assetId) ?? [];
|
|
1878
|
-
usages.push({
|
|
1879
|
-
...usage,
|
|
1880
|
-
blockIndex: assetUsage.blockIndex
|
|
1881
|
-
});
|
|
1882
|
-
usagesByAssetId.set(assetUsage.assetId, usages);
|
|
1883
|
-
}
|
|
1884
|
-
};
|
|
1885
|
-
for (const [locale, localeTranslations] of Object.entries(survey.translations ?? {})) {
|
|
1886
|
-
for (const scope of [
|
|
1887
|
-
"surveyCardContent",
|
|
1888
|
-
"navigationContent",
|
|
1889
|
-
"validationMessages"
|
|
1890
|
-
]) for (const [contentKey, content] of Object.entries(localeTranslations[scope] ?? {})) addContent(content, {
|
|
1891
|
-
locale,
|
|
1892
|
-
scope,
|
|
1893
|
-
contentKey
|
|
1894
|
-
});
|
|
1895
|
-
for (const [itemId, itemTranslations] of Object.entries(localeTranslations.itemTranslations ?? {})) for (const [contentKey, content] of Object.entries(itemTranslations ?? {})) addContent(content, {
|
|
1896
|
-
locale,
|
|
1897
|
-
scope: "itemTranslation",
|
|
1898
|
-
itemId,
|
|
1899
|
-
contentKey
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
return usagesByAssetId;
|
|
1903
|
-
};
|
|
1904
1935
|
const summarizeAssetSource = (asset) => ({
|
|
1905
1936
|
type: asset.source.type,
|
|
1906
1937
|
mediaType: asset.source.mediaType
|
|
1907
1938
|
});
|
|
1908
1939
|
const listAssets = (input, snapshot) => {
|
|
1909
1940
|
const survey = snapshot.survey;
|
|
1910
|
-
const
|
|
1941
|
+
const usages = snapshot.model.getAssetUsages();
|
|
1911
1942
|
const query = input.query?.trim().toLowerCase();
|
|
1912
1943
|
const allAssets = Object.entries(survey.assets ?? {}).map(([assetId, asset]) => {
|
|
1913
|
-
const
|
|
1944
|
+
const assetUsages = usages.filter((usage) => usage.assetId === assetId);
|
|
1914
1945
|
return {
|
|
1915
1946
|
assetId,
|
|
1916
1947
|
kind: asset.kind,
|
|
1917
1948
|
source: summarizeAssetSource(asset),
|
|
1918
1949
|
...asset.metadata ? { metadata: asset.metadata } : {},
|
|
1919
|
-
usageCount:
|
|
1920
|
-
usages
|
|
1950
|
+
usageCount: assetUsages.length,
|
|
1951
|
+
usages: assetUsages
|
|
1921
1952
|
};
|
|
1922
1953
|
}).filter((asset) => {
|
|
1923
1954
|
if (!query) return true;
|
|
@@ -1947,7 +1978,7 @@ const listAssets = (input, snapshot) => {
|
|
|
1947
1978
|
};
|
|
1948
1979
|
const surveyInspectTool = createTool({
|
|
1949
1980
|
id: TOOL_ID$3,
|
|
1950
|
-
description: "Inspect the current request-scoped survey draft through one compact interface: current context, item search, item details, paged
|
|
1981
|
+
description: "Inspect the current request-scoped survey draft through one compact interface: current context, item search, item details, paged multilingual localization surfaces, image assets with explicit asset IDs and usages, expression-reference diagnostics, or focused raw JSON. For expression diagnostics, valid means there are no blocking diagnostics; advisories remain counted. At turn start the draft equals the live snapshot; successful same-turn edits appear immediately.",
|
|
1951
1982
|
inputSchema: inputSchema$3,
|
|
1952
1983
|
outputSchema: outputSchema$2,
|
|
1953
1984
|
mcp: { annotations: {
|
|
@@ -1996,7 +2027,10 @@ const surveyInspectTool = createTool({
|
|
|
1996
2027
|
const inspection = runInspectionBackend(() => searchSurveyItems({
|
|
1997
2028
|
query,
|
|
1998
2029
|
limit: input.limit,
|
|
1999
|
-
cursor: input.cursor
|
|
2030
|
+
cursor: input.cursor,
|
|
2031
|
+
itemTypes: input.itemTypes,
|
|
2032
|
+
elementTypes: input.elementTypes,
|
|
2033
|
+
locales: input.locales
|
|
2000
2034
|
}, requestContext));
|
|
2001
2035
|
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
2002
2036
|
const result = inspection.value;
|
|
@@ -2037,15 +2071,16 @@ const surveyInspectTool = createTool({
|
|
|
2037
2071
|
const allDiagnostics = analyzeSurveyAssistantExpressionReferences(snapshot.survey, snapshot.trustedCapabilities);
|
|
2038
2072
|
const itemId = input.itemRef ? resolveItemRef(input, snapshot) : void 0;
|
|
2039
2073
|
const diagnostics = itemId ? allDiagnostics.filter((diagnostic) => diagnostic.ownerItemId === itemId) : allDiagnostics;
|
|
2040
|
-
const orderedDiagnostics = [...diagnostics].sort((left, right) => Number(
|
|
2041
|
-
const blockingCount = diagnostics.filter(
|
|
2074
|
+
const orderedDiagnostics = [...diagnostics].sort((left, right) => Number(right.severity === "error") - Number(left.severity === "error"));
|
|
2075
|
+
const blockingCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
2042
2076
|
const advisoryCount = diagnostics.length - blockingCount;
|
|
2043
2077
|
const limit = input.limit ?? 50;
|
|
2044
2078
|
const limitedDiagnostics = orderedDiagnostics.slice(0, limit);
|
|
2045
|
-
const
|
|
2079
|
+
const guidanceByCode = {};
|
|
2046
2080
|
for (const diagnostic of orderedDiagnostics) {
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2081
|
+
const guidance = getSurveyAssistantExpressionDiagnosticGuidance(diagnostic);
|
|
2082
|
+
const existing = ownValue(guidanceByCode, diagnostic.code);
|
|
2083
|
+
setOwnValue(guidanceByCode, diagnostic.code, existing && !existing.includes(guidance) ? `${existing} ${guidance}` : guidance);
|
|
2049
2084
|
}
|
|
2050
2085
|
const result = {
|
|
2051
2086
|
valid: blockingCount === 0 && visibilityIssues.length === 0,
|
|
@@ -2055,7 +2090,7 @@ const surveyInspectTool = createTool({
|
|
|
2055
2090
|
blockingCount,
|
|
2056
2091
|
advisoryCount,
|
|
2057
2092
|
diagnostics: limitedDiagnostics,
|
|
2058
|
-
|
|
2093
|
+
guidanceByCode,
|
|
2059
2094
|
truncated: diagnostics.length > limit
|
|
2060
2095
|
};
|
|
2061
2096
|
return {
|
|
@@ -2072,7 +2107,10 @@ const surveyInspectTool = createTool({
|
|
|
2072
2107
|
reason: "Localization inspection requires a targetLocale.",
|
|
2073
2108
|
modelSummary: "Localization inspection requires a targetLocale."
|
|
2074
2109
|
};
|
|
2075
|
-
const localization =
|
|
2110
|
+
const localization = inspectLocalization({
|
|
2111
|
+
...input,
|
|
2112
|
+
targetLocale: input.targetLocale
|
|
2113
|
+
}, snapshot);
|
|
2076
2114
|
if (!localization.ok) return {
|
|
2077
2115
|
ok: false,
|
|
2078
2116
|
action: input.action,
|
|
@@ -2129,7 +2167,10 @@ const surveyInspectTool = createTool({
|
|
|
2129
2167
|
result
|
|
2130
2168
|
};
|
|
2131
2169
|
},
|
|
2132
|
-
toModelOutput: (value) => toTextModelOutput(value, (output) => output.
|
|
2170
|
+
toModelOutput: (value) => toTextModelOutput(value, (output) => output.ok && output.action === "item-details" && isRecord(output.result) && output.result.ok === true ? JSON.stringify({
|
|
2171
|
+
capabilityDetails: output.result.capabilityDetails,
|
|
2172
|
+
...output.result
|
|
2173
|
+
}) : output.modelSummary)
|
|
2133
2174
|
});
|
|
2134
2175
|
//#endregion
|
|
2135
2176
|
//#region src/server/tools/survey-attachment-tool.ts
|
|
@@ -2326,14 +2367,14 @@ const formatAuditForModel = (output) => {
|
|
|
2326
2367
|
const location = [
|
|
2327
2368
|
finding.itemId,
|
|
2328
2369
|
finding.locale,
|
|
2329
|
-
finding.
|
|
2370
|
+
finding.localizationRefs ? JSON.stringify(finding.localizationRefs) : finding.path?.join("/")
|
|
2330
2371
|
].filter(Boolean).join(":");
|
|
2331
2372
|
return `- [${finding.severity}] ${finding.code}${location ? ` (${location})` : ""}: ${previewAuditMessage(finding.message)}${finding.suggestion ? ` Guidance: ${previewAuditMessage(finding.suggestion)}` : ""}`;
|
|
2332
2373
|
}).join("\n")}` : void 0,
|
|
2333
2374
|
report.qualityReviewPrinciples.length > 0 ? `Qualitative review rubric:\n${report.qualityReviewPrinciples.map((principle) => `- ${principle.id}: ${principle.prompt}`).join("\n")}` : void 0,
|
|
2334
2375
|
report.keyReview.entries.length > 0 ? `Key/content review evidence (${report.keyReview.offset + 1}-${report.keyReview.offset + report.keyReview.returnedEntryCount} of ${report.keyReview.entryCount}, source locale ${report.keyReview.sourceLocale ?? "unavailable"}${report.keyReview.truncated ? "; more pages available" : ""}):\n${report.keyReview.entries.map((entry) => {
|
|
2335
2376
|
const content = entry.content ? `; current content=${JSON.stringify(previewAuditMessage(entry.content))}` : "; current content unavailable";
|
|
2336
|
-
return `- ${entry.kind} item=${entry.itemId} itemKey=${JSON.stringify(entry.itemKey)} key=${JSON.stringify(entry.key)} (${entry.keySource})${entry.
|
|
2377
|
+
return `- ${entry.kind} item=${entry.itemId} itemKey=${JSON.stringify(entry.itemKey)} key=${JSON.stringify(entry.key)} (${entry.keySource})${entry.localizationRefs?.length ? ` contentRefs=${JSON.stringify(entry.localizationRefs)}` : ""}${content}`;
|
|
2337
2378
|
}).join("\n")}\n${report.keyReview.note}` : `Key/content review evidence: none returned (${report.keyReview.entryCount} total entries).`,
|
|
2338
2379
|
report.note,
|
|
2339
2380
|
"This audit checks the current survey deterministically. It does not compare the draft with an attached source document, so a clean audit does not establish source fidelity or completeness."
|
|
@@ -2384,21 +2425,7 @@ const inputSchema = z.object({
|
|
|
2384
2425
|
const slug = (value) => value.toLowerCase().replace(/\.[^.]+$/, "").replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100) || "image";
|
|
2385
2426
|
const OMITTED_EMBEDDED_IMAGE_DATA = "[embedded image data omitted from transcript]";
|
|
2386
2427
|
const getAssetReferenceLabels = (snapshot, assetId) => {
|
|
2387
|
-
|
|
2388
|
-
if (!translations) return [];
|
|
2389
|
-
const references = [];
|
|
2390
|
-
const inspectContent = (content, label) => {
|
|
2391
|
-
if (getAssetUsagesFromContent(content).some((usage) => usage.assetId === assetId)) references.push(label);
|
|
2392
|
-
};
|
|
2393
|
-
for (const [locale, localeTranslations] of Object.entries(translations)) {
|
|
2394
|
-
for (const surface of [
|
|
2395
|
-
"surveyCardContent",
|
|
2396
|
-
"navigationContent",
|
|
2397
|
-
"validationMessages"
|
|
2398
|
-
]) for (const [contentKey, content] of Object.entries(localeTranslations[surface] ?? {})) inspectContent(content, `${locale}:${surface}:${contentKey}`);
|
|
2399
|
-
for (const [itemId, itemTranslations] of Object.entries(localeTranslations.itemTranslations ?? {})) for (const [contentKey, content] of Object.entries(itemTranslations ?? {})) inspectContent(content, `${locale}:item:${itemId}:${contentKey}`);
|
|
2400
|
-
}
|
|
2401
|
-
return references;
|
|
2428
|
+
return snapshot?.model.getAssetUsages(assetId).map((usage) => `${usage.locale}:${usage.ref.scope === "content" ? `${usage.ref.ownerId}:${usage.ref.role}` : `${usage.ref.surface}:${usage.ref.contentKey}`}`) ?? [];
|
|
2402
2429
|
};
|
|
2403
2430
|
const getToolOutput = (value) => value && typeof value === "object" && "output" in value && value.output !== void 0 ? value.output : value;
|
|
2404
2431
|
const describeAssetOperation = (operation) => {
|
|
@@ -2418,8 +2445,8 @@ const summarizeAssetChange = (event) => {
|
|
|
2418
2445
|
lines.push(`Imported: ${event.operations.filter((operation) => operation.kind === "set-image-asset").map(describeAssetOperation).join(", ")}`);
|
|
2419
2446
|
lines.push(`Asset id to reference: ${importedAssetIds.join(", ")}`);
|
|
2420
2447
|
lines.push("The asset is now available to later mutation calls in this turn. To use it in a new item, reference it from that item's create-item translation. If a later delta is rejected, fix only that delta; this import remains in the draft.");
|
|
2421
|
-
lines.push(`
|
|
2422
|
-
lines.push(`
|
|
2448
|
+
lines.push(`Information element image source: { "type": "asset", "assetId": "${importedAssetIds[0]}" }. Embed the canonical image block inside the create-item translation richTextBlocks array.`);
|
|
2449
|
+
lines.push(`Information element translation example: { "locale": "<selected-locale>", "ownerId": "<information-element-id>", "role": "body", "richTextBlocks": [{ "type": "image", "source": { "type": "asset", "assetId": "${importedAssetIds[0]}" }, "alt": "<meaningful description>" }] }.`);
|
|
2423
2450
|
}
|
|
2424
2451
|
}
|
|
2425
2452
|
if (event.validation.issues.length > 0) lines.push(`Issues:\n${event.validation.issues.map((issue) => `- ${issue.code}: ${issue.message}`).join("\n")}`);
|
|
@@ -2474,7 +2501,7 @@ const surveyAssetTool = createTool({
|
|
|
2474
2501
|
let assetId = base;
|
|
2475
2502
|
let suffix = 2;
|
|
2476
2503
|
const stagedAssetIds = new Set(turnDraft.draftOperations.flatMap((draftOperation) => draftOperation.kind === "set-image-asset" ? [draftOperation.assetId] : []));
|
|
2477
|
-
while (snapshot?.survey.assets
|
|
2504
|
+
while (ownValue(snapshot?.survey.assets, assetId) || stagedAssetIds.has(assetId)) assetId = `${base}-${suffix++}`;
|
|
2478
2505
|
operation = {
|
|
2479
2506
|
kind: "set-image-asset",
|
|
2480
2507
|
assetId,
|
|
@@ -2525,7 +2552,7 @@ const surveyAssetTool = createTool({
|
|
|
2525
2552
|
validation: {
|
|
2526
2553
|
...event.validation,
|
|
2527
2554
|
valid: false,
|
|
2528
|
-
issues: [...event.validation.issues, {
|
|
2555
|
+
issues: [...event.validation.issues.filter((issue) => issue.code !== "image-asset-still-referenced"), {
|
|
2529
2556
|
severity: "error",
|
|
2530
2557
|
code: "image-asset-still-referenced",
|
|
2531
2558
|
message: `Image asset "${operation.assetId}" is still referenced by ${referenceLabels.slice(0, 3).join(", ")}. Prepare one survey_change delta containing every reference-removal operation first and remove-image-asset last.`,
|
|
@@ -2569,4 +2596,4 @@ const surveyAssistantTools = Object.freeze({
|
|
|
2569
2596
|
//#endregion
|
|
2570
2597
|
export { surveyAuditTool as i, surveyAssistantTools as n, surveyAssetTool as r, SURVEY_ASSISTANT_TOOL_IDS as t };
|
|
2571
2598
|
|
|
2572
|
-
//# sourceMappingURL=tools-
|
|
2599
|
+
//# sourceMappingURL=tools-D0KBDolR.mjs.map
|