@case-framework/survey-assistant 0.7.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/README.md +58 -3
- package/dist/{attachments-B0hg3vpT.mjs → attachments-BuNni5vB.mjs} +1 -1
- package/dist/{attachments-B0hg3vpT.mjs.map → attachments-BuNni5vB.mjs.map} +1 -1
- package/dist/{authoring-references-BJQ_1nMX.mjs → authoring-references-CmwLc_C8.mjs} +49 -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 +5 -12
- package/dist/capabilities-default.d.mts.map +1 -1
- package/dist/capabilities-default.mjs +2 -411
- package/dist/capabilities-z95e1mti.d.mts +117 -0
- package/dist/capabilities-z95e1mti.d.mts.map +1 -0
- package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
- package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
- package/dist/{constants-Be91Gay9.d.mts → constants-HL_klgJl.d.mts} +7 -28
- package/dist/constants-HL_klgJl.d.mts.map +1 -0
- package/dist/{controller-proxy-BH05e-C2.mjs → controller-proxy-BQ1YsVxJ.mjs} +12 -21
- package/dist/controller-proxy-BQ1YsVxJ.mjs.map +1 -0
- package/dist/controller-proxy-Cmzdr9tV.d.mts +65 -0
- package/dist/controller-proxy-Cmzdr9tV.d.mts.map +1 -0
- package/dist/default-D22AKTqE.mjs +233 -0
- package/dist/default-D22AKTqE.mjs.map +1 -0
- package/dist/{digest-SJmoYVaK.d.mts → digest-FnTTDBq2.d.mts} +2 -3
- package/dist/digest-FnTTDBq2.d.mts.map +1 -0
- package/dist/digest.d.mts +1 -1
- package/dist/digest.mjs.map +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/{fair-guidance-CRYCRxQm.mjs → fair-guidance-BmO4PswW.mjs} +1 -1
- package/dist/{fair-guidance-CRYCRxQm.mjs.map → fair-guidance-BmO4PswW.mjs.map} +1 -1
- package/dist/{index-l_EK4yK7.d.mts → index-B8TLBd1G.d.mts} +18 -72
- 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-x0qprHgK.d.mts → index-CeUlGkoU.d.mts} +58 -28
- package/dist/index-CeUlGkoU.d.mts.map +1 -0
- package/dist/{index-nhrgbgO5.d.mts → index-DYHdvSE8.d.mts} +347 -88
- 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-KWHQWFEW.mjs → memory-thread-repository-DmLadygY.mjs} +60 -30
- package/dist/memory-thread-repository-DmLadygY.mjs.map +1 -0
- 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 +4 -3
- package/dist/{react-B3ybjJ-X.mjs → react-DybJ5pWw.mjs} +159 -46
- package/dist/react-DybJ5pWw.mjs.map +1 -0
- package/dist/react-integration.d.mts +2 -2
- package/dist/react-integration.mjs +2 -2
- package/dist/react.d.mts +3 -3
- package/dist/react.mjs +3 -3
- package/dist/references/assistant-operations.md +224 -180
- package/dist/references/core-rules.md +6 -3
- package/dist/references/element-types.md +189 -0
- package/dist/references/expressions.md +307 -168
- package/dist/references/fair-by-design.md +9 -9
- package/dist/references/follow-ups.md +151 -0
- package/dist/references/localization.md +26 -12
- package/dist/references/response-variables.md +38 -0
- package/dist/references/rich-text-content.md +22 -43
- package/dist/references/source-material-surveys.md +20 -24
- package/dist/references/survey-data-model.md +36 -102
- package/dist/{request-context-CG4SWijt.mjs → request-context-KVh8VjgH.mjs} +4 -10
- package/dist/request-context-KVh8VjgH.mjs.map +1 -0
- package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
- package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
- package/dist/server-agent.d.mts +62 -43
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +73 -40
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +24 -42
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +12 -12
- package/dist/server-runtime.mjs.map +1 -1
- package/dist/server-tasks.d.mts +2 -2
- package/dist/server-tasks.mjs +2 -313
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +14 -36
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +5 -5
- package/dist/server.mjs.map +1 -1
- package/dist/storage-postgres.d.mts +9 -25
- package/dist/storage-postgres.d.mts.map +1 -1
- package/dist/storage-postgres.mjs +5 -4
- package/dist/storage-postgres.mjs.map +1 -1
- package/dist/tasks-Coh5N027.mjs +397 -0
- package/dist/tasks-Coh5N027.mjs.map +1 -0
- package/dist/{tasks-CYqrcaRO.d.mts → tasks-wb9M-bak.d.mts} +25 -41
- package/dist/tasks-wb9M-bak.d.mts.map +1 -0
- package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
- package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
- package/dist/{thread-handlers-DBAaU7BA.d.mts → thread-handlers-C3Iya92v.d.mts} +9 -40
- package/dist/thread-handlers-C3Iya92v.d.mts.map +1 -0
- package/dist/{tools-BvSovehT.mjs → tools-D0KBDolR.mjs} +677 -459
- 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.css +1 -1
- package/dist/ui.d.mts +15 -54
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +203 -156
- package/dist/ui.mjs.map +1 -1
- package/docs/integration.md +11 -1
- package/package.json +41 -36
- package/dist/authoring-references-BJQ_1nMX.mjs.map +0 -1
- package/dist/capabilities-1KNnNO1_.mjs +0 -73
- package/dist/capabilities-1KNnNO1_.mjs.map +0 -1
- package/dist/capabilities-DVTfogjv.d.mts +0 -193
- package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
- package/dist/capabilities-default.mjs.map +0 -1
- package/dist/constants-Be91Gay9.d.mts.map +0 -1
- package/dist/controller-proxy-BH05e-C2.mjs.map +0 -1
- package/dist/controller-proxy-Bd0ZHBPF.d.mts +0 -85
- package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +0 -1
- package/dist/digest-SJmoYVaK.d.mts.map +0 -1
- package/dist/engine-Bo85Oyj0.mjs +0 -6723
- package/dist/engine-Bo85Oyj0.mjs.map +0 -1
- package/dist/index-Tnqv-yKW.d.mts +0 -777
- package/dist/index-Tnqv-yKW.d.mts.map +0 -1
- package/dist/index-l_EK4yK7.d.mts.map +0 -1
- package/dist/index-nhrgbgO5.d.mts.map +0 -1
- package/dist/index-x0qprHgK.d.mts.map +0 -1
- package/dist/lifecycle-Bx0aq_4S.d.mts +0 -529
- package/dist/lifecycle-Bx0aq_4S.d.mts.map +0 -1
- package/dist/memory-thread-repository-KWHQWFEW.mjs.map +0 -1
- package/dist/protocol-BhzQbx81.mjs +0 -1173
- package/dist/protocol-BhzQbx81.mjs.map +0 -1
- package/dist/react-B3ybjJ-X.mjs.map +0 -1
- package/dist/references/embedded-forms.md +0 -126
- package/dist/references/item-types.md +0 -407
- package/dist/request-context-CG4SWijt.mjs.map +0 -1
- package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
- package/dist/server-tasks.mjs.map +0 -1
- package/dist/tasks-CYqrcaRO.d.mts.map +0 -1
- package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
- package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
- package/dist/thread-handlers-DBAaU7BA.d.mts.map +0 -1
- package/dist/tools-BvSovehT.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,64 +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
|
};
|
|
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.",
|
|
96
|
+
"```json",
|
|
97
|
+
JSON.stringify(z.toJSONSchema(schema, {
|
|
98
|
+
io: "input",
|
|
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),
|
|
106
|
+
"```"
|
|
107
|
+
].join("\n\n");
|
|
71
108
|
const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
72
109
|
const { $schema: _schemaVersion, ...contract } = z.toJSONSchema(surveyOperationSchemasByKind[kind], {
|
|
73
110
|
io: "input",
|
|
74
|
-
reused: "
|
|
111
|
+
reused: "ref",
|
|
75
112
|
unrepresentable: "any"
|
|
76
113
|
});
|
|
77
114
|
const detail = options.detail ?? "compact";
|
|
78
115
|
const renderedContract = detail === "full" ? contract : compactJsonSchema(contract);
|
|
79
|
-
const
|
|
116
|
+
const elementCapability = options.elementCapability;
|
|
80
117
|
return [
|
|
81
118
|
`# \`${kind}\` operation contract`,
|
|
82
119
|
"",
|
|
@@ -84,12 +121,23 @@ const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
|
84
121
|
"the canonical post-normalization field shape. Cross-field and snapshot-dependent rules are",
|
|
85
122
|
"enforced when the proposal is validated. Follow focused authoring guidance when it recommends",
|
|
86
123
|
"a supported model-facing shorthand; otherwise use this canonical shape.",
|
|
87
|
-
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.",
|
|
88
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."] : [],
|
|
89
136
|
"```json",
|
|
90
|
-
JSON.stringify(renderedContract, null, 2),
|
|
137
|
+
JSON.stringify(renderedContract, null, detail === "full" ? 2 : void 0),
|
|
91
138
|
"```",
|
|
92
|
-
...
|
|
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)] : []
|
|
93
141
|
].join("\n");
|
|
94
142
|
};
|
|
95
143
|
//#endregion
|
|
@@ -167,7 +215,7 @@ const TOOL_ID$6 = "survey_authoring_reference";
|
|
|
167
215
|
const topicSchema = z.string().min(1).max(120);
|
|
168
216
|
const inputSchema$10 = z.object({
|
|
169
217
|
topic: topicSchema.describe("The narrow authoring reference to load."),
|
|
170
|
-
|
|
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."),
|
|
171
219
|
operationKind: z.string().min(1).max(120).optional().describe("For topic assistant-operations, return only this operation's generated canonical field contract."),
|
|
172
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."),
|
|
173
221
|
reason: z.string().max(500).optional().describe("Short reason this reference is needed.")
|
|
@@ -175,7 +223,7 @@ const inputSchema$10 = z.object({
|
|
|
175
223
|
const outputSchema$8 = z.object({
|
|
176
224
|
ok: z.boolean(),
|
|
177
225
|
topic: topicSchema,
|
|
178
|
-
|
|
226
|
+
elementType: z.string().optional(),
|
|
179
227
|
operationKind: z.string().optional(),
|
|
180
228
|
detail: z.enum(SURVEY_OPERATION_CONTRACT_DETAIL_LEVELS).optional(),
|
|
181
229
|
title: z.string(),
|
|
@@ -187,7 +235,7 @@ const formatCanonicalNameRecovery = (input, candidates, noun) => {
|
|
|
187
235
|
};
|
|
188
236
|
const surveyAuthoringReferenceTool = createTool({
|
|
189
237
|
id: TOOL_ID$6,
|
|
190
|
-
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.",
|
|
191
239
|
inputSchema: inputSchema$10,
|
|
192
240
|
outputSchema: outputSchema$8,
|
|
193
241
|
mcp: { annotations: {
|
|
@@ -200,12 +248,20 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
200
248
|
execute: async (inputData, context) => {
|
|
201
249
|
const contractDetail = inputData.detail ?? "compact";
|
|
202
250
|
const engineContext = getSurveyAssistantEngineContext(context?.requestContext);
|
|
203
|
-
const
|
|
204
|
-
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
|
+
};
|
|
205
261
|
if (inputData.operationKind && inputData.topic !== "assistant-operations") return {
|
|
206
262
|
ok: false,
|
|
207
263
|
topic: inputData.topic,
|
|
208
|
-
|
|
264
|
+
elementType: inputData.elementType,
|
|
209
265
|
operationKind: inputData.operationKind,
|
|
210
266
|
title: "Operation contract unavailable",
|
|
211
267
|
content: "operationKind is supported only with topic assistant-operations."
|
|
@@ -219,61 +275,65 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
219
275
|
title: "Operation contract unavailable",
|
|
220
276
|
content: `No built-in survey_change operation named "${inputData.operationKind}" is available.` + formatCanonicalNameRecovery(inputData.operationKind, SURVEY_OPERATION_KINDS, "operation")
|
|
221
277
|
};
|
|
222
|
-
if (inputData.
|
|
278
|
+
if (inputData.elementType && !elementCapability && !getBuiltInElementTypeAuthoringReference(inputData.elementType)) return {
|
|
223
279
|
ok: false,
|
|
224
280
|
topic: inputData.topic,
|
|
225
|
-
|
|
281
|
+
elementType: inputData.elementType,
|
|
226
282
|
operationKind: inputData.operationKind,
|
|
227
283
|
detail: contractDetail,
|
|
228
284
|
title: "Item capability unavailable",
|
|
229
|
-
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")
|
|
230
286
|
};
|
|
231
|
-
if (
|
|
287
|
+
if (elementCapability && !elementCapability.supportedOperations.includes(inputData.operationKind)) return {
|
|
232
288
|
ok: false,
|
|
233
289
|
topic: inputData.topic,
|
|
234
|
-
|
|
290
|
+
elementType: inputData.elementType,
|
|
235
291
|
operationKind: inputData.operationKind,
|
|
236
292
|
detail: contractDetail,
|
|
237
293
|
title: "Operation unsupported for item type",
|
|
238
|
-
content: `Item type "${
|
|
294
|
+
content: `Item type "${elementCapability.elementType}" does not support survey_change operation "${inputData.operationKind}".`
|
|
239
295
|
};
|
|
240
296
|
return {
|
|
241
297
|
ok: true,
|
|
242
298
|
topic: inputData.topic,
|
|
243
|
-
|
|
299
|
+
elementType: inputData.elementType,
|
|
244
300
|
operationKind: inputData.operationKind,
|
|
245
301
|
detail: contractDetail,
|
|
246
|
-
title:
|
|
302
|
+
title: elementCapability ? `${inputData.operationKind} for ${elementCapability.displayName}` : `${inputData.operationKind} operation contract`,
|
|
247
303
|
content: formatSurveyOperationContractReference(inputData.operationKind, {
|
|
248
304
|
detail: contractDetail,
|
|
249
|
-
|
|
305
|
+
elementCapability,
|
|
306
|
+
elementSchema: inputData.elementType ? engineContext?.trustedCapabilities?.registry.get(inputData.elementType).schema : void 0
|
|
250
307
|
})
|
|
251
308
|
};
|
|
252
309
|
}
|
|
253
310
|
const builtInEntry = surveyAuthoringReferenceTopics.includes(inputData.topic) ? surveyAuthoringReferences[inputData.topic] : void 0;
|
|
254
|
-
const capability = inputData.
|
|
255
|
-
const capabilityReference = inputData.
|
|
311
|
+
const capability = inputData.elementType ? engineContext?.trustedCapabilities?.byElementType.get(inputData.elementType) : void 0;
|
|
312
|
+
const capabilityReference = inputData.elementType ? resolveAssistantCapabilityReference({
|
|
256
313
|
capabilities: engineContext?.trustedCapabilities,
|
|
257
|
-
|
|
314
|
+
elementType: inputData.elementType,
|
|
258
315
|
topic: inputData.topic
|
|
259
316
|
}) : void 0;
|
|
260
|
-
const
|
|
261
|
-
if (inputData.
|
|
317
|
+
const focusedBuiltInElementTypeReference = inputData.topic === "element-types" && inputData.elementType ? getBuiltInElementTypeAuthoringReference(inputData.elementType) : void 0;
|
|
318
|
+
if (inputData.elementType && !focusedBuiltInElementTypeReference && !capability && !capabilityReference) return {
|
|
262
319
|
ok: false,
|
|
263
320
|
topic: inputData.topic,
|
|
264
|
-
|
|
321
|
+
elementType: inputData.elementType,
|
|
265
322
|
title: "Item capability unavailable",
|
|
266
|
-
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")
|
|
267
324
|
};
|
|
268
325
|
const capabilityEntry = capabilityReference ? {
|
|
269
326
|
title: capabilityReference.title ?? `${capability?.manifest.displayName} authoring`,
|
|
270
327
|
content: capabilityReference.markdown
|
|
271
328
|
} : void 0;
|
|
272
|
-
const focusedCapabilityProfile = inputData.topic === "
|
|
273
|
-
title:
|
|
274
|
-
content:
|
|
329
|
+
const focusedCapabilityProfile = inputData.topic === "element-types" && elementCapability ? {
|
|
330
|
+
title: elementCapability.displayName,
|
|
331
|
+
content: formatElementCapabilityProfile(elementCapability)
|
|
275
332
|
} : void 0;
|
|
276
|
-
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;
|
|
277
337
|
const entry = baseEntry && capabilityEntry ? {
|
|
278
338
|
title: baseEntry.title,
|
|
279
339
|
content: baseEntry.content === capabilityEntry.content ? baseEntry.content : [
|
|
@@ -286,18 +346,18 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
286
346
|
if (!entry) return {
|
|
287
347
|
ok: false,
|
|
288
348
|
topic: inputData.topic,
|
|
289
|
-
|
|
349
|
+
elementType: inputData.elementType,
|
|
290
350
|
operationKind: inputData.operationKind,
|
|
291
351
|
title: "Reference unavailable",
|
|
292
|
-
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")
|
|
293
353
|
};
|
|
294
354
|
return {
|
|
295
355
|
ok: true,
|
|
296
356
|
topic: inputData.topic,
|
|
297
|
-
|
|
357
|
+
elementType: inputData.elementType,
|
|
298
358
|
operationKind: inputData.operationKind,
|
|
299
359
|
title: entry.title,
|
|
300
|
-
content: entry.content
|
|
360
|
+
content: inputData.topic === "element-types" && inputData.elementType && engineContext?.trustedCapabilities ? `${entry.content}\n\n${formatElementContract(engineContext.trustedCapabilities.registry.get(inputData.elementType).schema)}` : entry.content
|
|
301
361
|
};
|
|
302
362
|
},
|
|
303
363
|
toModelOutput: (value) => toTextModelOutput(value, (output) => `# ${output.title}\n\n${output.content}`)
|
|
@@ -318,24 +378,18 @@ const summarizePendingRepairs = (event) => {
|
|
|
318
378
|
//#region src/server/tools/survey-change-tool.ts
|
|
319
379
|
const TOOL_ID$5 = "survey_change";
|
|
320
380
|
const OPERATIONS_DESCRIPTION = [
|
|
321
|
-
"Typed
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"Use update-
|
|
327
|
-
"
|
|
328
|
-
|
|
329
|
-
"
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"
|
|
333
|
-
`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.`}`,
|
|
334
|
-
"Use patch-item-config with itemId and plural patches whose JSON Pointer paths are relative to that item config root.",
|
|
335
|
-
"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.",
|
|
336
|
-
"When deleting referenced images and assets, include every reference-removal operation and remove-image-asset last in the same operations array.",
|
|
337
|
-
"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.",
|
|
338
|
-
"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."
|
|
339
393
|
].join(" ");
|
|
340
394
|
const inputSchema$9 = z.object({
|
|
341
395
|
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
@@ -362,39 +416,8 @@ const rejectedOperationParseEvent = ({ issueText, summary, turnId }) => ({
|
|
|
362
416
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
363
417
|
});
|
|
364
418
|
const describeOperationTarget = (operation) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
case "update-placed-image": return `${operation.kind} ${operation.locale}:${operation.itemId}:${operation.contentKey}#${operation.imageIndex}`;
|
|
368
|
-
case "update-item-label":
|
|
369
|
-
case "update-item-color":
|
|
370
|
-
case "update-item-key":
|
|
371
|
-
case "patch-item-config":
|
|
372
|
-
case "remove-item":
|
|
373
|
-
case "move-item": return `${operation.kind} ${operation.itemId}`;
|
|
374
|
-
case "upsert-form-field-group": return `${operation.kind} ${operation.target.itemId}:${operation.group.id}`;
|
|
375
|
-
case "remove-form-field-group": return `${operation.kind} ${operation.target.itemId}:${operation.groupId}`;
|
|
376
|
-
case "reorder-form-field-groups": return `${operation.kind} ${operation.target.itemId}`;
|
|
377
|
-
case "upsert-form-field": return `${operation.kind} ${operation.target.itemId}:${operation.field.id}`;
|
|
378
|
-
case "remove-form-field":
|
|
379
|
-
case "move-form-field": return `${operation.kind} ${operation.target.itemId}:${operation.fieldId}`;
|
|
380
|
-
case "set-embedded-form":
|
|
381
|
-
case "remove-embedded-form": return `${operation.kind} ${operation.itemId}:${operation.optionId}`;
|
|
382
|
-
case "add-choice-option": return `${operation.kind} ${operation.itemId}:${operation.option.id ?? operation.option.key ?? operation.option.label}`;
|
|
383
|
-
case "update-choice-option-label":
|
|
384
|
-
case "remove-choice-option": return `${operation.kind} ${operation.itemId}:${operation.optionId}`;
|
|
385
|
-
case "reorder-choice-options": return `${operation.kind} ${operation.itemId}`;
|
|
386
|
-
case "reorder-items": return `${operation.kind} ${operation.parentItemId}`;
|
|
387
|
-
case "update-survey-metadata":
|
|
388
|
-
case "update-survey-pagination": return operation.kind;
|
|
389
|
-
case "update-survey-translation": return `${operation.kind} ${operation.locale}:${operation.surface}:${operation.contentKey}`;
|
|
390
|
-
case "add-survey-locale":
|
|
391
|
-
case "remove-survey-locale": return `${operation.kind} ${operation.locale}`;
|
|
392
|
-
case "set-image-asset":
|
|
393
|
-
case "update-image-asset-metadata":
|
|
394
|
-
case "remove-image-asset": return `${operation.kind} ${operation.assetId}`;
|
|
395
|
-
case "create-item": return `${operation.kind} ${operation.item.id ?? operation.item.key ?? operation.item.itemType}`;
|
|
396
|
-
case "survey-json-patch": return `${operation.kind} ${operation.patches.map((patch) => patch.path).join(", ")}`;
|
|
397
|
-
}
|
|
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}`;
|
|
398
421
|
};
|
|
399
422
|
const summarizeValidation = (event) => {
|
|
400
423
|
const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} survey change: ${event.summary}`];
|
|
@@ -410,11 +433,19 @@ const summarizeValidation = (event) => {
|
|
|
410
433
|
lines.push(`Draft updated (${event.operations.length} total operation${event.operations.length === 1 ? "" : "s"}). Later tools can inspect and edit the resulting draft state. The live editor is unchanged until successful turn completion.`);
|
|
411
434
|
if (event.postChange) {
|
|
412
435
|
const unavailableItemCount = event.postChange.touchedItemCount - event.postChange.availableTouchedItemCount;
|
|
413
|
-
lines.push(`Canonical post-change preview (first
|
|
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` : ""}.`);
|
|
414
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.`);
|
|
415
438
|
for (const item of event.postChange.touchedItems) {
|
|
416
|
-
const translations = item.translations.map(({
|
|
417
|
-
lines.push(`- ${item.
|
|
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("; ");
|
|
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"}`);
|
|
441
|
+
if (item.placement) {
|
|
442
|
+
const p = item.placement;
|
|
443
|
+
lines.push(` Stored placement: parent ${p.parentItemId}; index ${p.index} (zero-based), ${p.index + 1}/${p.siblingCount}; previous ${p.previousItemId ?? "none"}; next ${p.nextItemId ?? "none"}.${p.shuffled ? " Parent shuffles children; respondent order may differ." : ""}`);
|
|
444
|
+
}
|
|
445
|
+
if (item.childOrder) {
|
|
446
|
+
const order = item.childOrder;
|
|
447
|
+
lines.push(` Stored child order (${order.itemIds.length}/${order.total}${order.truncated ? "; truncated" : ""}): ${JSON.stringify(order.itemIds)}.${order.shuffled ? " Group shuffles children; respondent order may differ." : ""}`);
|
|
448
|
+
}
|
|
418
449
|
}
|
|
419
450
|
if (event.postChange.truncated) lines.push("The post-change preview is truncated. The validated draft still contains the complete change; inspect any relevant omitted target or field before claiming its exact post-change value.");
|
|
420
451
|
}
|
|
@@ -427,7 +458,7 @@ const summarizeValidation = (event) => {
|
|
|
427
458
|
const location = [
|
|
428
459
|
finding.itemId,
|
|
429
460
|
finding.locale,
|
|
430
|
-
finding.
|
|
461
|
+
finding.localizationRefs ? JSON.stringify(finding.localizationRefs) : void 0
|
|
431
462
|
].filter(Boolean).join(":");
|
|
432
463
|
lines.push(`- ${finding.severity} ${finding.code}${location ? ` (${location})` : ""}: ${finding.messagePreview}${finding.truncated ? `… [truncated preview; ${finding.messageLength} characters total]` : ""}`);
|
|
433
464
|
}
|
|
@@ -438,7 +469,6 @@ const summarizeValidation = (event) => {
|
|
|
438
469
|
lines.push("Issues:");
|
|
439
470
|
for (const issue of event.validation.issues) lines.push(`- ${issue.code}: ${issue.message}`);
|
|
440
471
|
const issueCodes = new Set(event.validation.issues.map((issue) => issue.code));
|
|
441
|
-
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.");
|
|
442
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.");
|
|
443
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.");
|
|
444
474
|
}
|
|
@@ -500,12 +530,12 @@ const inputSchema$8 = z.object({
|
|
|
500
530
|
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
501
531
|
action: z.enum(["context", "prepare"]).describe("Choose exactly one envelope. Inspect with action=\"context\" and optional reason, itemIds, or includeRawLocations. Create edits with action=\"prepare\", summary, and mutations."),
|
|
502
532
|
reason: z.string().max(500).optional().describe("Context action only: optional inspection reason."),
|
|
503
|
-
itemIds: z.array(z.string().min(1).max(240)).max(12).optional().describe("Context action only: filter response slots and expression locations by item
|
|
533
|
+
itemIds: z.array(z.string().min(1).max(240)).max(12).optional().describe("Context action only: filter response slots and expression locations by exact item IDs. Omit to inspect survey-wide references, including controlling questions outside the conditioned item."),
|
|
504
534
|
cursor: z.string().regex(/^\d+$/).max(12).optional().describe("Context action only: nextCursor from the preceding page, with the same itemIds and includeRawLocations. Restart after a draft mutation."),
|
|
505
535
|
limit: z.number().int().min(1).max(80).optional().describe("Context action only: rows per catalog page; defaults to 80."),
|
|
506
536
|
includeRawLocations: z.boolean().optional().describe("Context action only: include existing expression locations by raw item."),
|
|
507
537
|
summary: z.string().max(500).optional().describe("Prepare action only: required user-facing summary."),
|
|
508
|
-
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.`)
|
|
509
539
|
}).strict().superRefine((value, ctx) => {
|
|
510
540
|
if (value.action === "context") {
|
|
511
541
|
if (value.summary?.trim()) ctx.addIssue({
|
|
@@ -535,19 +565,31 @@ const contextOutputSchema = z.object({
|
|
|
535
565
|
ok: z.boolean(),
|
|
536
566
|
action: z.literal("context"),
|
|
537
567
|
modelSummary: z.string(),
|
|
538
|
-
listOrdering:
|
|
568
|
+
listOrdering: stringRecord(z.string(), z.string()).describe("Meaning of each returned list. Expression references are not positional."),
|
|
539
569
|
nextCursor: z.string().optional(),
|
|
540
570
|
totalResponseSlots: z.number().int().nonnegative().optional(),
|
|
541
571
|
surveyResponseSlotCount: z.number().int().nonnegative().optional(),
|
|
542
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(),
|
|
543
580
|
unresolvedItemIds: z.array(z.string()).optional(),
|
|
544
581
|
responseSlots: z.array(z.object({
|
|
545
|
-
ref: z.
|
|
546
|
-
|
|
582
|
+
ref: z.object({
|
|
583
|
+
slotId: z.string(),
|
|
584
|
+
method: z.enum(["get", "isDefined"])
|
|
585
|
+
}),
|
|
586
|
+
valueType: z.string().optional(),
|
|
587
|
+
variableName: z.string().optional(),
|
|
588
|
+
itemId: z.string().optional()
|
|
547
589
|
})),
|
|
548
590
|
expressionLocations: z.array(z.object({
|
|
549
591
|
itemId: z.string(),
|
|
550
|
-
|
|
592
|
+
editorName: z.string(),
|
|
551
593
|
itemType: z.string(),
|
|
552
594
|
rawIndex: z.number().int().nonnegative(),
|
|
553
595
|
locations: z.array(z.string())
|
|
@@ -558,7 +600,6 @@ const createIssue = ({ severity = "error", ...input }) => ({
|
|
|
558
600
|
severity,
|
|
559
601
|
...input
|
|
560
602
|
});
|
|
561
|
-
const buildResponseSlotTypes = (snapshot) => snapshot.responseSlotTypes;
|
|
562
603
|
const rejectedEvent = ({ issues, summary, turnId, warnings = [] }) => ({
|
|
563
604
|
protocolVersion: 1,
|
|
564
605
|
id: randomUUID(),
|
|
@@ -591,39 +632,52 @@ const summarizeDraftEvent = (event) => {
|
|
|
591
632
|
};
|
|
592
633
|
const buildContextOutput = (snapshot, input) => {
|
|
593
634
|
const rawItems = getRawSurveyItems(snapshot);
|
|
635
|
+
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
594
636
|
const resolvedItems = input.itemIds?.map((identifier) => ({
|
|
595
637
|
identifier,
|
|
596
|
-
itemId: snapshot.inspection.
|
|
638
|
+
itemId: snapshot.inspection.itemIndex.find((entry) => entry.itemId === identifier)?.itemId
|
|
597
639
|
}));
|
|
598
640
|
const itemFilter = resolvedItems?.length ? new Set(resolvedItems.flatMap((entry) => entry.itemId ? [entry.itemId] : [])) : void 0;
|
|
599
641
|
const unresolvedItemIds = resolvedItems?.filter((entry) => !entry.itemId).map((entry) => entry.identifier) ?? [];
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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"
|
|
604
664
|
}));
|
|
605
665
|
const offset = Number(input.cursor ?? 0);
|
|
606
666
|
const limit = input.limit ?? 80;
|
|
607
667
|
const responseSlots = allResponseSlots.slice(offset, offset + limit);
|
|
608
668
|
const allExpressionLocations = input.action === "context" && input.includeRawLocations === true ? rawItems.map((item, rawIndex) => {
|
|
609
|
-
if (itemFilter && !itemFilter.has(item.id)
|
|
610
|
-
const locations = [
|
|
611
|
-
item.displayConditions?.root ? "displayConditions.root" : void 0,
|
|
612
|
-
...Object.keys(item.displayConditions?.components ?? {}).map((componentId) => `displayConditions.components.${componentId}`),
|
|
613
|
-
...Object.keys(item.disabledConditions?.components ?? {}).map((componentId) => `disabledConditions.components.${componentId}`),
|
|
614
|
-
...Object.keys(item.validations ?? {}).map((validationKey) => `validations.${validationKey}`),
|
|
615
|
-
...(item.prefills ?? []).map((prefill) => `prefills.${prefill.id}`)
|
|
616
|
-
].filter((location) => Boolean(location));
|
|
669
|
+
if (itemFilter && !itemFilter.has(item.id)) return;
|
|
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("/")}`);
|
|
617
671
|
return {
|
|
618
672
|
itemId: item.id,
|
|
619
|
-
|
|
673
|
+
editorName: names.get(item.id) ?? item.id,
|
|
620
674
|
itemType: item.itemType,
|
|
621
675
|
rawIndex,
|
|
622
676
|
locations
|
|
623
677
|
};
|
|
624
678
|
}).filter((entry) => Boolean(entry)) : void 0;
|
|
625
679
|
const expressionLocations = allExpressionLocations?.slice(offset, offset + limit);
|
|
626
|
-
const totalAvailable = Math.max(allResponseSlots.length, allExpressionLocations?.length ?? 0);
|
|
680
|
+
const totalAvailable = Math.max(allResponseSlots.length, allStateTargets.length, allExpressionLocations?.length ?? 0);
|
|
627
681
|
const nextCursor = offset + limit < totalAvailable ? String(offset + limit) : void 0;
|
|
628
682
|
return {
|
|
629
683
|
ok: true,
|
|
@@ -631,31 +685,36 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
631
685
|
modelSummary: [
|
|
632
686
|
"Expression capability loaded.",
|
|
633
687
|
"Use prepare mutations for display conditions, disabled conditions, validations, prefills, and template values.",
|
|
634
|
-
"
|
|
688
|
+
"Visibility, disabled and validation expressions must return boolean.",
|
|
635
689
|
"Use response refs exactly as listed and check their value types before choosing functions.",
|
|
636
|
-
"
|
|
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.",
|
|
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.",
|
|
637
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.",
|
|
638
|
-
"
|
|
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.",
|
|
639
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[].",
|
|
640
696
|
"Response-slot order: set-like deterministic snapshot construction order; it has no survey or respondent-visible meaning.",
|
|
641
|
-
itemFilter ? `Response-slot scope: filtered to itemIds=${JSON.stringify(input.itemIds)}; ${allResponseSlots.length} matching of ${
|
|
642
|
-
`
|
|
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))}`,
|
|
699
|
+
`Response slots (${responseSlots.length}/${allResponseSlots.length}, offset ${offset}): ${responseSlots.map((slot) => `${JSON.stringify(slot.ref)}${slot.valueType ? `:${slot.valueType}` : ""}`).join(", ")}`,
|
|
643
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.",
|
|
644
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,
|
|
645
|
-
"Functions: and, or, not, eq, gt, gte, lt, lte, in_range, list_contains, sum, min, max, str_eq, date_eq.",
|
|
646
|
-
expressionLocations ? `Existing expression locations: ${expressionLocations.filter((entry) => entry.locations.length > 0).map((entry) => `${entry.
|
|
702
|
+
"Functions: and, or, not, eq, gt, gte, lt, lte, in_range, list_contains, list_count, if, sum, min, max, str_eq, date_eq.",
|
|
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
|
|
647
704
|
].filter((line) => Boolean(line)).join("\n"),
|
|
648
705
|
totalResponseSlots: allResponseSlots.length,
|
|
649
|
-
surveyResponseSlotCount:
|
|
706
|
+
surveyResponseSlotCount: snapshot.responseVariables.length * 2,
|
|
650
707
|
...allExpressionLocations ? { totalExpressionLocations: allExpressionLocations.length } : {},
|
|
651
708
|
...nextCursor ? { nextCursor } : {},
|
|
652
709
|
...unresolvedItemIds.length ? { unresolvedItemIds } : {},
|
|
653
710
|
listOrdering: {
|
|
654
711
|
responseSlots: "Set-like deterministic snapshot construction order; position has no survey or respondent-visible meaning.",
|
|
655
712
|
expressionLocations: "Stored surveyItems array order; locations within an item are grouped by expression surface, not ranked or respondent-visible order.",
|
|
656
|
-
"expressionLocations[].locations": "
|
|
713
|
+
"expressionLocations[].locations": "Canonical schema traversal order; paths locate conditions, rules, prefills and element expressions."
|
|
657
714
|
},
|
|
658
715
|
responseSlots,
|
|
716
|
+
stateTargets: allStateTargets.slice(offset, offset + limit),
|
|
717
|
+
totalStateTargets: allStateTargets.length,
|
|
659
718
|
...expressionLocations ? { expressionLocations } : {}
|
|
660
719
|
};
|
|
661
720
|
};
|
|
@@ -719,7 +778,7 @@ const surveyExpressionTool = createTool({
|
|
|
719
778
|
}), collectExpressionMutationRecoveryTargets(input.mutations, snapshot));
|
|
720
779
|
const prepareInput = prepareParse.data;
|
|
721
780
|
const addressedTargets = collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot);
|
|
722
|
-
const { issues, patches } = buildExpressionMutationBatch(prepareInput.mutations, snapshot);
|
|
781
|
+
const { issues, patches } = buildExpressionMutationBatch(prepareInput.mutations, snapshot.model);
|
|
723
782
|
const errors = issues.filter((issue) => issue.severity === "error");
|
|
724
783
|
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
725
784
|
if (errors.length > 0) return reject(rejectedEvent({
|
|
@@ -765,6 +824,104 @@ const surveyExpressionTool = createTool({
|
|
|
765
824
|
toModelOutput: (value) => toTextModelOutput(value, (output) => "action" in output && output.action === "context" ? output.modelSummary : summarizeDraftEvent(output))
|
|
766
825
|
});
|
|
767
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
|
|
768
925
|
//#region src/server/tools/parse-inspection-output.ts
|
|
769
926
|
const parseInspectionOutput = (schema, value, backend) => {
|
|
770
927
|
const parsed = schema.safeParse(value);
|
|
@@ -772,6 +929,69 @@ const parseInspectionOutput = (schema, value, backend) => {
|
|
|
772
929
|
return parsed.data;
|
|
773
930
|
};
|
|
774
931
|
//#endregion
|
|
932
|
+
//#region src/server/tools/response-variable-summary.ts
|
|
933
|
+
const MAX_COMPONENTS = 50;
|
|
934
|
+
const responseVariableSummarySchema = z.object({
|
|
935
|
+
slotId: z.string(),
|
|
936
|
+
itemId: z.string(),
|
|
937
|
+
variableName: z.string(),
|
|
938
|
+
valueType: z.string(),
|
|
939
|
+
elementId: z.string().optional(),
|
|
940
|
+
rowId: z.string().optional(),
|
|
941
|
+
columnId: z.string().optional(),
|
|
942
|
+
namingKind: z.enum(["variable", "matrix"]).optional(),
|
|
943
|
+
computed: z.object({
|
|
944
|
+
operation: z.string(),
|
|
945
|
+
sourceSlotIds: z.array(z.string())
|
|
946
|
+
}).optional(),
|
|
947
|
+
computedSourcesTruncated: z.boolean(),
|
|
948
|
+
referenceValueKeys: stringRecord(z.string(), z.string()).optional(),
|
|
949
|
+
referenceValueScores: stringRecord(z.string(), z.number()).optional(),
|
|
950
|
+
optionIds: z.array(z.string()),
|
|
951
|
+
optionsTotal: z.number(),
|
|
952
|
+
optionsTruncated: z.boolean()
|
|
953
|
+
});
|
|
954
|
+
const responseOptionIds = (slot) => [.../* @__PURE__ */ new Set([
|
|
955
|
+
...Object.keys(slot.referenceValueKeys ?? {}),
|
|
956
|
+
...Object.keys(slot.referenceValueScores ?? {}),
|
|
957
|
+
...(slot.allowedValues ?? []).flatMap((value) => value.type === "reference" ? [value.value] : value.type === "reference[]" ? value.value : [])
|
|
958
|
+
])];
|
|
959
|
+
/** Coding discovery excludes config paths and duplicated translations for every survey locale. */
|
|
960
|
+
const summarizeResponseVariable = (slot) => {
|
|
961
|
+
const optionIds = responseOptionIds(slot);
|
|
962
|
+
const take = (values) => values && Object.fromEntries(optionIds.slice(0, MAX_COMPONENTS).filter((id) => Object.hasOwn(values, id)).map((id) => [id, values[id]]));
|
|
963
|
+
return {
|
|
964
|
+
slotId: slot.slotId,
|
|
965
|
+
itemId: slot.itemId,
|
|
966
|
+
variableName: slot.variableName,
|
|
967
|
+
valueType: slot.valueType,
|
|
968
|
+
elementId: slot.elementId,
|
|
969
|
+
rowId: slot.rowId,
|
|
970
|
+
columnId: slot.columnId,
|
|
971
|
+
namingKind: slot.naming?.kind,
|
|
972
|
+
computed: slot.computed && {
|
|
973
|
+
...slot.computed,
|
|
974
|
+
sourceSlotIds: slot.computed.sourceSlotIds.slice(0, MAX_COMPONENTS)
|
|
975
|
+
},
|
|
976
|
+
computedSourcesTruncated: (slot.computed?.sourceSlotIds.length ?? 0) > MAX_COMPONENTS,
|
|
977
|
+
referenceValueKeys: take(slot.referenceValueKeys),
|
|
978
|
+
referenceValueScores: take(slot.referenceValueScores),
|
|
979
|
+
optionIds: optionIds.slice(0, MAX_COMPONENTS),
|
|
980
|
+
optionsTotal: optionIds.length,
|
|
981
|
+
optionsTruncated: optionIds.length > MAX_COMPONENTS
|
|
982
|
+
};
|
|
983
|
+
};
|
|
984
|
+
const responseVariableMatchesQuery = (slot, query) => [
|
|
985
|
+
slot.slotId,
|
|
986
|
+
slot.itemId,
|
|
987
|
+
slot.variableName,
|
|
988
|
+
slot.elementId,
|
|
989
|
+
slot.rowId,
|
|
990
|
+
slot.columnId,
|
|
991
|
+
...responseOptionIds(slot),
|
|
992
|
+
...Object.values(slot.referenceValueKeys ?? {})
|
|
993
|
+
].some((value) => value?.toLowerCase().includes(query));
|
|
994
|
+
//#endregion
|
|
775
995
|
//#region src/server/tools/inspect-current-context.ts
|
|
776
996
|
const contextScopeSchema = z.enum([
|
|
777
997
|
"summary",
|
|
@@ -788,15 +1008,16 @@ const outlineModeSchema$1 = z.enum([
|
|
|
788
1008
|
]);
|
|
789
1009
|
const translationSnippetSchema = z.object({
|
|
790
1010
|
locale: z.string(),
|
|
791
|
-
|
|
1011
|
+
ownerId: z.string(),
|
|
1012
|
+
role: z.string(),
|
|
792
1013
|
text: z.string()
|
|
793
1014
|
});
|
|
794
1015
|
const inputSchema$7 = z.object({
|
|
795
1016
|
scopes: z.array(contextScopeSchema).min(1).default(["summary"]).describe("Which current editor context sections to inspect."),
|
|
796
1017
|
itemId: z.string().optional().describe("Optional item id to inspect. Defaults to the selected/focused item."),
|
|
797
|
-
query: z.string().max(180).optional().describe("Optional text used to find matching item
|
|
1018
|
+
query: z.string().max(180).optional().describe("Optional text used to find matching item IDs, editor names, labels, or response variables."),
|
|
798
1019
|
cursor: z.string().optional().describe("Optional numeric cursor returned by a previous outline or response slot request."),
|
|
799
|
-
outlineMode: outlineModeSchema$1.default("full").optional().describe("For outline scope: \"full\" pages the complete survey
|
|
1020
|
+
outlineMode: outlineModeSchema$1.default("full").optional().describe("For outline scope: \"full\" pages the complete survey item index, \"focused\" returns the selected/focused neighborhood, and \"children\" returns direct children of itemId."),
|
|
800
1021
|
limit: z.number().int().min(1).max(50).default(12).describe("Maximum number of rows to return for list-like scopes.")
|
|
801
1022
|
});
|
|
802
1023
|
const outputSchema$6 = z.object({
|
|
@@ -805,11 +1026,12 @@ const outputSchema$6 = z.object({
|
|
|
805
1026
|
revision: z.number().int().nonnegative().optional(),
|
|
806
1027
|
contentHash: z.string().optional(),
|
|
807
1028
|
scopes: z.array(contextScopeSchema).optional(),
|
|
808
|
-
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."),
|
|
809
1030
|
summary: z.object({
|
|
810
1031
|
sessionId: z.string().nullable(),
|
|
811
1032
|
view: z.string(),
|
|
812
1033
|
selectedItemId: z.string().optional(),
|
|
1034
|
+
responseSlotId: z.string().optional(),
|
|
813
1035
|
focusItemId: z.string().optional(),
|
|
814
1036
|
selectedLocale: z.string(),
|
|
815
1037
|
recentItemIds: z.array(z.string()),
|
|
@@ -820,9 +1042,9 @@ const outputSchema$6 = z.object({
|
|
|
820
1042
|
outlineItemCount: z.number().int().nonnegative(),
|
|
821
1043
|
responseSlotCount: z.number().int().nonnegative(),
|
|
822
1044
|
rootItem: itemSummarySchema.optional(),
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1045
|
+
availableElementTypes: z.array(z.string()),
|
|
1046
|
+
deterministicCreateElementTypes: z.array(z.string()),
|
|
1047
|
+
elementTypeDetails: z.array(assistantElementTypeManifestSchema)
|
|
826
1048
|
}).optional(),
|
|
827
1049
|
selectedItem: z.object({
|
|
828
1050
|
available: z.boolean(),
|
|
@@ -859,7 +1081,7 @@ const outputSchema$6 = z.object({
|
|
|
859
1081
|
nextCursor: z.string().optional(),
|
|
860
1082
|
truncated: z.boolean(),
|
|
861
1083
|
ordering: z.string(),
|
|
862
|
-
slots: z.array(
|
|
1084
|
+
slots: z.array(responseVariableSummarySchema),
|
|
863
1085
|
note: z.string().optional()
|
|
864
1086
|
}).optional(),
|
|
865
1087
|
uiContext: z.object({
|
|
@@ -871,7 +1093,13 @@ const outputSchema$6 = z.object({
|
|
|
871
1093
|
componentId: z.string().optional(),
|
|
872
1094
|
label: z.string().optional(),
|
|
873
1095
|
details: z.string().optional()
|
|
874
|
-
}))
|
|
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()
|
|
875
1103
|
}).optional(),
|
|
876
1104
|
history: z.object({
|
|
877
1105
|
currentIndex: z.number().int(),
|
|
@@ -913,13 +1141,14 @@ const flattenFocusDescendants = (node) => {
|
|
|
913
1141
|
};
|
|
914
1142
|
const getCandidateItems = (snapshot) => {
|
|
915
1143
|
const outlineById = mapOutlineById(snapshot);
|
|
916
|
-
return
|
|
1144
|
+
return summarizeItemIndexEntries(getItemIndexEntries(snapshot), outlineById);
|
|
917
1145
|
};
|
|
918
1146
|
const buildSummary = (snapshot) => ({
|
|
919
1147
|
rootItem: snapshot.inspection.outline.filter((item) => item.depth === 0).map(summarizeOutlineNode)[0],
|
|
920
1148
|
sessionId: snapshot.version.sessionId,
|
|
921
1149
|
view: snapshot.ui.view,
|
|
922
1150
|
selectedItemId: snapshot.ui.selectedItemId,
|
|
1151
|
+
responseSlotId: snapshot.ui.responseSlotId,
|
|
923
1152
|
focusItemId: snapshot.ui.focusItemId,
|
|
924
1153
|
selectedLocale: snapshot.ui.selectedLocale,
|
|
925
1154
|
recentItemIds: snapshot.ui.recentItemIds ?? [],
|
|
@@ -927,15 +1156,15 @@ const buildSummary = (snapshot) => ({
|
|
|
927
1156
|
locales: snapshot.inspection.locales,
|
|
928
1157
|
itemCount: snapshot.inspection.itemCount,
|
|
929
1158
|
historyIndex: snapshot.version.historyIndex,
|
|
930
|
-
outlineItemCount: snapshot.inspection.
|
|
1159
|
+
outlineItemCount: snapshot.inspection.itemIndex.length,
|
|
931
1160
|
responseSlotCount: snapshot.inspection.responseSlots.length,
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1161
|
+
availableElementTypes: snapshot.capabilities.elementTypes.map((entry) => entry.elementType),
|
|
1162
|
+
deterministicCreateElementTypes: getDeterministicCreateElementTypes(snapshot.capabilities),
|
|
1163
|
+
elementTypeDetails: snapshot.capabilities.elementTypes
|
|
935
1164
|
});
|
|
936
1165
|
const buildSelectedItem = (snapshot, input) => {
|
|
937
1166
|
const outlineById = mapOutlineById(snapshot);
|
|
938
|
-
const indexById =
|
|
1167
|
+
const indexById = mapItemIndexById(snapshot);
|
|
939
1168
|
const resolvedInputItem = input.itemId ? resolveItemSummary(input.itemId, indexById, outlineById) : void 0;
|
|
940
1169
|
const requestedItemId = resolvedInputItem?.itemId ?? input.itemId ?? snapshot.ui.selectedItemId ?? snapshot.ui.focusItemId;
|
|
941
1170
|
if (!requestedItemId) return {
|
|
@@ -968,11 +1197,12 @@ const buildSelectedItem = (snapshot, input) => {
|
|
|
968
1197
|
const buildOutline = (snapshot, input) => {
|
|
969
1198
|
const query = normalizeText(input.query);
|
|
970
1199
|
const outlineById = mapOutlineById(snapshot);
|
|
971
|
-
const indexById =
|
|
1200
|
+
const indexById = mapItemIndexById(snapshot);
|
|
972
1201
|
const resolvedInputItem = input.itemId ? resolveItemSummary(input.itemId, indexById, outlineById) : void 0;
|
|
973
1202
|
const requestedItemId = resolvedInputItem?.itemId ?? input.itemId ?? snapshot.ui.focusItemId ?? snapshot.ui.selectedItemId;
|
|
974
1203
|
const outlineMode = input.outlineMode ?? "full";
|
|
975
1204
|
let strategy = "focused-snapshot-outline";
|
|
1205
|
+
let parentShuffled;
|
|
976
1206
|
let items;
|
|
977
1207
|
let note;
|
|
978
1208
|
if (query) {
|
|
@@ -981,8 +1211,12 @@ const buildOutline = (snapshot, input) => {
|
|
|
981
1211
|
} else if (outlineMode === "children" && requestedItemId) {
|
|
982
1212
|
strategy = "direct-children";
|
|
983
1213
|
const parent = resolvedInputItem ?? summarizeItemById(requestedItemId, indexById, outlineById);
|
|
984
|
-
items = parent ?
|
|
985
|
-
if (!parent) note = "The requested item could not be resolved by item
|
|
1214
|
+
items = parent ? findDirectChildrenByParentId(getItemIndexEntries(snapshot), outlineById, parent.itemId) : [];
|
|
1215
|
+
if (!parent) note = "The requested item could not be resolved by exact item ID.";
|
|
1216
|
+
else {
|
|
1217
|
+
const rawParent = snapshot.survey.surveyItems.find((item) => item.id === parent.itemId);
|
|
1218
|
+
if (rawParent?.itemType === "group") parentShuffled = rawParent.config.shuffleItems === true;
|
|
1219
|
+
}
|
|
986
1220
|
} else if (outlineMode === "focused" && requestedItemId && snapshot.inspection.focus?.focusItemId === requestedItemId) {
|
|
987
1221
|
const focus = snapshot.inspection.focus;
|
|
988
1222
|
const descendantIds = snapshot.inspection.focusItem ? flattenFocusDescendants(snapshot.inspection.focusItem).map((item) => item.itemId) : [];
|
|
@@ -999,7 +1233,7 @@ const buildOutline = (snapshot, input) => {
|
|
|
999
1233
|
items = item ? [item] : [];
|
|
1000
1234
|
note = "The requested item is outside the selected focus tree in this snapshot.";
|
|
1001
1235
|
} else {
|
|
1002
|
-
strategy = "full-
|
|
1236
|
+
strategy = "full-item-index";
|
|
1003
1237
|
items = getCandidateItems(snapshot);
|
|
1004
1238
|
}
|
|
1005
1239
|
const page = sliceWithCursor(items, input);
|
|
@@ -1012,15 +1246,21 @@ const buildOutline = (snapshot, input) => {
|
|
|
1012
1246
|
returned: page.items.length,
|
|
1013
1247
|
nextCursor: page.nextCursor,
|
|
1014
1248
|
truncated: page.truncated,
|
|
1015
|
-
ordering:
|
|
1249
|
+
ordering: {
|
|
1250
|
+
"full-item-index": "Survey-tree preorder: configured group-child order is preserved; unparented items are appended by item id. Shuffled groups may render a different respondent order.",
|
|
1251
|
+
"direct-children": parentShuffled === void 0 ? "No resolved parent group; this result does not establish sibling order." : parentShuffled ? "Stored group-child order; this group shuffles children, so respondent order may differ." : "Stored group-child order; this group does not shuffle children.",
|
|
1252
|
+
"query-match": "Survey-tree preorder after filtering by the query; this is not relevance-ranked.",
|
|
1253
|
+
"item-match": "Single matched item; this does not establish sibling order.",
|
|
1254
|
+
"focused-snapshot-outline": "Relationship grouping for the focused item, not a single positional survey order."
|
|
1255
|
+
}[strategy],
|
|
1016
1256
|
items: page.items,
|
|
1017
1257
|
note
|
|
1018
1258
|
};
|
|
1019
1259
|
};
|
|
1020
1260
|
const buildResponseSlots = (snapshot, input) => {
|
|
1021
|
-
const responseSlots = snapshot.
|
|
1261
|
+
const responseSlots = snapshot.responseVariables;
|
|
1022
1262
|
const query = normalizeText(input.query);
|
|
1023
|
-
const filtered = query ? responseSlots.filter((slot) => slot
|
|
1263
|
+
const filtered = query ? responseSlots.filter((slot) => responseVariableMatchesQuery(slot, query)) : responseSlots;
|
|
1024
1264
|
const page = sliceWithCursor(filtered, input);
|
|
1025
1265
|
return {
|
|
1026
1266
|
available: true,
|
|
@@ -1030,7 +1270,7 @@ const buildResponseSlots = (snapshot, input) => {
|
|
|
1030
1270
|
nextCursor: page.nextCursor,
|
|
1031
1271
|
truncated: page.truncated,
|
|
1032
1272
|
ordering: "Set-like response references in deterministic snapshot construction order; do not interpret their position as survey or respondent order.",
|
|
1033
|
-
slots: page.items
|
|
1273
|
+
slots: page.items.map(summarizeResponseVariable)
|
|
1034
1274
|
};
|
|
1035
1275
|
};
|
|
1036
1276
|
const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
@@ -1049,9 +1289,9 @@ const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
|
1049
1289
|
if (scopes.includes("summary")) Object.assign(listOrdering, {
|
|
1050
1290
|
"summary.recentItemIds": "Most-recent-first UI history; not survey structure.",
|
|
1051
1291
|
"summary.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1052
|
-
"summary.
|
|
1053
|
-
"summary.
|
|
1054
|
-
"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."
|
|
1055
1295
|
});
|
|
1056
1296
|
if (scopes.includes("selected-item")) Object.assign(listOrdering, {
|
|
1057
1297
|
"selectedItem.pathItems": "Root-to-selected survey-tree path.",
|
|
@@ -1079,7 +1319,8 @@ const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
|
1079
1319
|
if (scopes.includes("ui-context")) output.uiContext = {
|
|
1080
1320
|
activeSurface: snapshot.ui.activeSurface,
|
|
1081
1321
|
openPanels: snapshot.ui.openPanels ?? [],
|
|
1082
|
-
highlights: snapshot.ui.highlights ?? []
|
|
1322
|
+
highlights: snapshot.ui.highlights ?? [],
|
|
1323
|
+
...snapshot.ui.task ? { task: snapshot.ui.task } : {}
|
|
1083
1324
|
};
|
|
1084
1325
|
if (scopes.includes("history") && snapshot.ui.history) output.history = snapshot.ui.history;
|
|
1085
1326
|
return parseInspectionOutput(outputSchema$6, output, "Current-context inspection");
|
|
@@ -1087,12 +1328,13 @@ const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
|
1087
1328
|
//#endregion
|
|
1088
1329
|
//#region src/server/tools/inspect-item-details.ts
|
|
1089
1330
|
const inputSchema$6 = z.object({
|
|
1090
|
-
|
|
1331
|
+
cursor: z.string().optional(),
|
|
1332
|
+
responseVariablesLimit: z.number().int().min(1).max(50).default(20),
|
|
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."),
|
|
1091
1334
|
locales: z.array(z.string()).optional().describe("Optional locale codes to restrict returned translations to. Defaults to all locales."),
|
|
1092
1335
|
textLimit: z.number().int().min(20).max(2e3).default(400).describe("Maximum characters returned per translation snippet."),
|
|
1093
|
-
includeChildren: z.boolean().optional().describe("Deprecated alias for includeSurveyChildren. This means survey-tree children only, not choice options or form fields."),
|
|
1094
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."),
|
|
1095
|
-
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."),
|
|
1096
1338
|
childrenLimit: z.number().int().min(1).max(50).default(20).describe("Maximum number of direct survey-tree children to return.")
|
|
1097
1339
|
});
|
|
1098
1340
|
const outputSchema$5 = z.object({
|
|
@@ -1102,63 +1344,75 @@ const outputSchema$5 = z.object({
|
|
|
1102
1344
|
translations: z.array(translationEntrySchema).optional(),
|
|
1103
1345
|
surveyChildren: z.array(itemSummarySchema).optional(),
|
|
1104
1346
|
surveyChildrenTruncated: z.boolean().optional(),
|
|
1105
|
-
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."),
|
|
1106
1348
|
configDetails: itemConfigDetailsSchema.optional(),
|
|
1107
|
-
|
|
1349
|
+
responseVariables: z.array(responseVariableSummarySchema).optional(),
|
|
1350
|
+
responseVariablesTotal: z.number().optional(),
|
|
1351
|
+
responseVariablesTruncated: z.boolean().optional(),
|
|
1352
|
+
responseVariablesNextCursor: z.string().optional(),
|
|
1353
|
+
responseVariablesNote: z.string().optional(),
|
|
1354
|
+
capabilityDetails: z.array(z.object({
|
|
1355
|
+
elementId: z.string(),
|
|
1356
|
+
elementType: z.string(),
|
|
1108
1357
|
summary: z.string(),
|
|
1109
1358
|
details: z.unknown().optional()
|
|
1110
|
-
}).optional()
|
|
1359
|
+
})).optional()
|
|
1111
1360
|
});
|
|
1112
1361
|
const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
1113
|
-
const input = inputSchema$6.parse(
|
|
1114
|
-
...inputData,
|
|
1115
|
-
includeSurveyChildren: inputData.includeSurveyChildren ?? inputData.includeChildren
|
|
1116
|
-
});
|
|
1362
|
+
const input = inputSchema$6.parse(inputData);
|
|
1117
1363
|
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1118
1364
|
if (!snapshot) return {
|
|
1119
1365
|
ok: false,
|
|
1120
1366
|
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1121
1367
|
};
|
|
1122
1368
|
const outlineById = mapOutlineById(snapshot);
|
|
1123
|
-
const indexById =
|
|
1369
|
+
const indexById = mapItemIndexById(snapshot);
|
|
1124
1370
|
const rawItemsById = mapRawItemsById(snapshot);
|
|
1125
1371
|
const item = resolveItemSummary(input.itemId, indexById, outlineById);
|
|
1126
1372
|
if (!item) return {
|
|
1127
1373
|
ok: false,
|
|
1128
|
-
reason: `No item matching "${input.itemId}" (by itemId
|
|
1374
|
+
reason: `No item matching "${input.itemId}" (by exact itemId) was found in the current context snapshot.`
|
|
1129
1375
|
};
|
|
1130
1376
|
const rawItem = rawItemsById.get(item.itemId);
|
|
1131
|
-
const rawConfig = rawItem?.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1132
1377
|
const output = {
|
|
1133
1378
|
ok: true,
|
|
1134
1379
|
item,
|
|
1135
1380
|
translations: buildItemTranslationEntries(snapshot, item.itemId, input),
|
|
1136
1381
|
listOrdering: {
|
|
1137
|
-
translations: "Grouped by locale and then
|
|
1138
|
-
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."
|
|
1139
1384
|
}
|
|
1140
1385
|
};
|
|
1141
1386
|
if (input.includeSurveyChildren) {
|
|
1142
|
-
const
|
|
1387
|
+
const entries = getItemIndexEntries(snapshot);
|
|
1388
|
+
const allChildren = findDirectChildrenByParentId(entries, outlineById, item.itemId);
|
|
1143
1389
|
const limit = clampLimit(input.childrenLimit);
|
|
1144
1390
|
output.surveyChildren = allChildren.slice(0, limit);
|
|
1145
1391
|
output.surveyChildrenTruncated = allChildren.length > limit;
|
|
1146
1392
|
}
|
|
1147
1393
|
if (input.includeItemConfig) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
})
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1394
|
+
const variables = snapshot.responseVariables.filter((slot) => slot.itemId === item.itemId);
|
|
1395
|
+
const page = sliceWithCursor(variables, {
|
|
1396
|
+
cursor: input.cursor,
|
|
1397
|
+
limit: input.responseVariablesLimit
|
|
1398
|
+
});
|
|
1399
|
+
output.responseVariables = page.items.map(summarizeResponseVariable);
|
|
1400
|
+
output.responseVariablesTotal = variables.length;
|
|
1401
|
+
output.responseVariablesTruncated = page.truncated;
|
|
1402
|
+
output.responseVariablesNextCursor = page.nextCursor;
|
|
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
|
+
});
|
|
1162
1416
|
}
|
|
1163
1417
|
return parseInspectionOutput(outputSchema$5, output, "Item-details inspection");
|
|
1164
1418
|
};
|
|
@@ -1166,17 +1420,17 @@ const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
|
1166
1420
|
//#region src/server/tools/inspect-raw-survey.ts
|
|
1167
1421
|
const DEFAULT_VALUE_CHARACTER_LIMIT = 12e3;
|
|
1168
1422
|
const MAX_VALUE_CHARACTER_LIMIT = 6e4;
|
|
1169
|
-
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\".");
|
|
1170
1424
|
const inputSchema$5 = z.object({
|
|
1171
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."),
|
|
1172
|
-
itemIdentifiers: z.array(z.string().min(1).max(240)).min(1).max(20).optional().describe("Optional item
|
|
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."),
|
|
1173
1427
|
textOffset: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
1174
1428
|
valueCharacterLimit: z.number().int().min(1e3).max(MAX_VALUE_CHARACTER_LIMIT).default(DEFAULT_VALUE_CHARACTER_LIMIT).describe("Maximum serialized characters returned per requested value.")
|
|
1175
1429
|
});
|
|
1176
1430
|
const rawSurveyItemPathSchema = z.object({
|
|
1177
1431
|
path: z.string(),
|
|
1178
1432
|
id: z.string(),
|
|
1179
|
-
|
|
1433
|
+
editorName: z.string(),
|
|
1180
1434
|
itemType: z.string(),
|
|
1181
1435
|
parentId: z.string().optional(),
|
|
1182
1436
|
childCount: z.number().int().nonnegative().optional()
|
|
@@ -1186,7 +1440,7 @@ const outputSchema$4 = z.object({
|
|
|
1186
1440
|
reason: z.string().optional(),
|
|
1187
1441
|
revision: z.number().int().nonnegative().optional(),
|
|
1188
1442
|
contentHash: z.string().optional(),
|
|
1189
|
-
listOrdering:
|
|
1443
|
+
listOrdering: stringRecord(z.string(), z.string()).optional().describe("Meaning of each returned list. Raw JSON arrays retain their stored order."),
|
|
1190
1444
|
overview: z.object({
|
|
1191
1445
|
topLevelKeys: z.array(z.string()),
|
|
1192
1446
|
surveyItemCount: z.number().int().nonnegative(),
|
|
@@ -1216,7 +1470,7 @@ const outputSchema$4 = z.object({
|
|
|
1216
1470
|
rawItemPreview: z.string().optional(),
|
|
1217
1471
|
rawItemTruncated: z.boolean().optional(),
|
|
1218
1472
|
translationPaths: z.array(z.string()).optional(),
|
|
1219
|
-
translationsByLocale:
|
|
1473
|
+
translationsByLocale: stringRecord(z.string(), z.unknown()).optional(),
|
|
1220
1474
|
translationsPreview: z.string().optional(),
|
|
1221
1475
|
translationsTruncated: z.boolean().optional(),
|
|
1222
1476
|
error: z.string().optional()
|
|
@@ -1237,6 +1491,7 @@ const buildOverview = (snapshot) => {
|
|
|
1237
1491
|
const rawSurvey = getRawSurvey(snapshot);
|
|
1238
1492
|
const rawItems = getRawSurveyItems(snapshot);
|
|
1239
1493
|
const parentByChildId = buildParentByChildId(rawSurvey);
|
|
1494
|
+
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
1240
1495
|
return {
|
|
1241
1496
|
topLevelKeys: Object.keys(rawSurvey).sort(),
|
|
1242
1497
|
surveyItemCount: rawItems.length,
|
|
@@ -1244,7 +1499,7 @@ const buildOverview = (snapshot) => {
|
|
|
1244
1499
|
itemPaths: rawItems.map((item, index) => ({
|
|
1245
1500
|
path: `/surveyItems/${index}`,
|
|
1246
1501
|
id: item.id,
|
|
1247
|
-
|
|
1502
|
+
editorName: names.get(item.id) ?? item.id,
|
|
1248
1503
|
itemType: item.itemType,
|
|
1249
1504
|
parentId: parentByChildId.get(item.id),
|
|
1250
1505
|
childCount: getGroupChildren(item).length || void 0
|
|
@@ -1305,7 +1560,7 @@ const findRawItemByIdentifier = (snapshot, identifier) => {
|
|
|
1305
1560
|
item: rawItems[directIndex],
|
|
1306
1561
|
index: directIndex
|
|
1307
1562
|
};
|
|
1308
|
-
const resolved = resolveItemSummary(identifier,
|
|
1563
|
+
const resolved = resolveItemSummary(identifier, mapItemIndexById(snapshot), mapOutlineById(snapshot));
|
|
1309
1564
|
if (!resolved) return;
|
|
1310
1565
|
const resolvedIndex = rawItems.findIndex((item) => item.id === resolved.itemId);
|
|
1311
1566
|
if (resolvedIndex < 0) return;
|
|
@@ -1314,26 +1569,30 @@ const findRawItemByIdentifier = (snapshot, identifier) => {
|
|
|
1314
1569
|
index: resolvedIndex
|
|
1315
1570
|
};
|
|
1316
1571
|
};
|
|
1317
|
-
const getTranslationsByLocaleForItem = (
|
|
1572
|
+
const getTranslationsByLocaleForItem = (snapshot, itemId) => {
|
|
1318
1573
|
const entries = {};
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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);
|
|
1322
1581
|
}
|
|
1323
1582
|
return entries;
|
|
1324
1583
|
};
|
|
1325
1584
|
const buildItemValues = (snapshot, input) => {
|
|
1326
|
-
const rawSurvey = getRawSurvey(snapshot);
|
|
1327
1585
|
return input.itemIdentifiers.map((identifier) => {
|
|
1328
1586
|
const resolved = findRawItemByIdentifier(snapshot, identifier);
|
|
1329
1587
|
if (!resolved) return {
|
|
1330
1588
|
identifier,
|
|
1331
1589
|
found: false,
|
|
1332
|
-
error: `No item matching "${identifier}" was found by item
|
|
1590
|
+
error: `No item matching "${identifier}" was found by exact item ID.`
|
|
1333
1591
|
};
|
|
1334
1592
|
const itemId = resolved.item.id;
|
|
1335
1593
|
const rawItemOutput = toOutputValue(resolved.item, input.valueCharacterLimit);
|
|
1336
|
-
const
|
|
1594
|
+
const translationsByLocale = getTranslationsByLocaleForItem(snapshot, itemId);
|
|
1595
|
+
const translationsOutput = toOutputValue(translationsByLocale, input.valueCharacterLimit);
|
|
1337
1596
|
const translationsValue = "value" in translationsOutput && isRecord$1(translationsOutput.value) ? translationsOutput.value : void 0;
|
|
1338
1597
|
return {
|
|
1339
1598
|
identifier,
|
|
@@ -1343,7 +1602,7 @@ const buildItemValues = (snapshot, input) => {
|
|
|
1343
1602
|
rawItem: "value" in rawItemOutput ? rawItemOutput.value : void 0,
|
|
1344
1603
|
rawItemPreview: "preview" in rawItemOutput ? rawItemOutput.preview : void 0,
|
|
1345
1604
|
rawItemTruncated: rawItemOutput.truncated,
|
|
1346
|
-
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)}`)),
|
|
1347
1606
|
translationsByLocale: translationsValue,
|
|
1348
1607
|
translationsPreview: "preview" in translationsOutput ? translationsOutput.preview : void 0,
|
|
1349
1608
|
translationsTruncated: translationsOutput.truncated
|
|
@@ -1393,7 +1652,8 @@ const searchMatchSchema = z.object({
|
|
|
1393
1652
|
]),
|
|
1394
1653
|
field: z.string().optional(),
|
|
1395
1654
|
locale: z.string().optional(),
|
|
1396
|
-
|
|
1655
|
+
ownerId: z.string().optional(),
|
|
1656
|
+
role: z.string().optional(),
|
|
1397
1657
|
text: z.string(),
|
|
1398
1658
|
score: z.number()
|
|
1399
1659
|
});
|
|
@@ -1404,7 +1664,8 @@ const searchResultSchema = z.object({
|
|
|
1404
1664
|
});
|
|
1405
1665
|
const inputSchema$4 = z.object({
|
|
1406
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\"."),
|
|
1407
|
-
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."),
|
|
1408
1669
|
locales: z.array(z.string()).optional().describe("Optional locale codes to restrict translation-content matches to."),
|
|
1409
1670
|
cursor: z.string().optional().describe("Optional numeric cursor returned by a previous search."),
|
|
1410
1671
|
limit: z.number().int().min(1).max(50).default(12).describe("Maximum number of matching items to return.")
|
|
@@ -1420,7 +1681,7 @@ const outputSchema$3 = z.object({
|
|
|
1420
1681
|
ordering: z.string().optional().describe("How results and their matches are ordered; this search output is not survey order."),
|
|
1421
1682
|
results: z.array(searchResultSchema).optional()
|
|
1422
1683
|
});
|
|
1423
|
-
const STOP_WORDS = new Set([
|
|
1684
|
+
const STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1424
1685
|
"a",
|
|
1425
1686
|
"about",
|
|
1426
1687
|
"an",
|
|
@@ -1463,7 +1724,7 @@ const scoreText = (value, query, terms, baseScore) => {
|
|
|
1463
1724
|
const addMatch = (matches, match) => {
|
|
1464
1725
|
const text = match.text?.trim();
|
|
1465
1726
|
if (!text) return;
|
|
1466
|
-
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({
|
|
1467
1728
|
...match,
|
|
1468
1729
|
text
|
|
1469
1730
|
});
|
|
@@ -1473,7 +1734,7 @@ const candidateItems = (snapshot) => {
|
|
|
1473
1734
|
const rawItemsById = mapRawItemsById(snapshot);
|
|
1474
1735
|
const seen = /* @__PURE__ */ new Set();
|
|
1475
1736
|
const candidates = [];
|
|
1476
|
-
for (const entry of
|
|
1737
|
+
for (const entry of summarizeItemIndexEntries(snapshot.inspection.itemIndex, outlineById)) {
|
|
1477
1738
|
seen.add(entry.itemId);
|
|
1478
1739
|
candidates.push({
|
|
1479
1740
|
item: entry,
|
|
@@ -1492,13 +1753,21 @@ const candidateItems = (snapshot) => {
|
|
|
1492
1753
|
const searchItems = (snapshot, input) => {
|
|
1493
1754
|
const terms = queryTerms(input.query);
|
|
1494
1755
|
const itemTypeFilter = input.itemTypes?.length ? new Set(input.itemTypes) : void 0;
|
|
1495
|
-
|
|
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 }) => {
|
|
1496
1764
|
const matches = [];
|
|
1497
1765
|
const structuralFields = [
|
|
1498
1766
|
["itemId", item.itemId],
|
|
1499
1767
|
["itemType", item.itemType],
|
|
1500
|
-
["
|
|
1501
|
-
["
|
|
1768
|
+
["elementTypes", [...typesByItem.get(item.itemId) ?? []].join(" ")],
|
|
1769
|
+
["key", item.editorName],
|
|
1770
|
+
["breadcrumb", item.breadcrumb],
|
|
1502
1771
|
["itemLabel", item.itemLabel],
|
|
1503
1772
|
["path", item.path?.join(" ")]
|
|
1504
1773
|
];
|
|
@@ -1516,16 +1785,17 @@ const searchItems = (snapshot, input) => {
|
|
|
1516
1785
|
locales: input.locales,
|
|
1517
1786
|
textLimit: DEFAULT_TEXT_LIMIT
|
|
1518
1787
|
})) {
|
|
1519
|
-
const score = scoreText(`${translation.
|
|
1788
|
+
const score = scoreText(`${translation.ownerId} ${translation.role} ${translation.text}`, input.query, terms, 100);
|
|
1520
1789
|
if (score !== null) addMatch(matches, {
|
|
1521
1790
|
source: "translation",
|
|
1522
1791
|
locale: translation.locale,
|
|
1523
|
-
|
|
1792
|
+
ownerId: translation.ownerId,
|
|
1793
|
+
role: translation.role,
|
|
1524
1794
|
text: translation.text,
|
|
1525
1795
|
score
|
|
1526
1796
|
});
|
|
1527
1797
|
}
|
|
1528
|
-
if (rawItem) for (const text of buildConfigSearchText(rawItem)) {
|
|
1798
|
+
if (rawItem) for (const text of buildConfigSearchText(rawItem, snapshot.model)) {
|
|
1529
1799
|
const score = scoreText(text, input.query, terms, 60);
|
|
1530
1800
|
if (score !== null) addMatch(matches, {
|
|
1531
1801
|
source: "config",
|
|
@@ -1540,7 +1810,7 @@ const searchItems = (snapshot, input) => {
|
|
|
1540
1810
|
matches: matches.slice(0, MAX_MATCHES_PER_ITEM),
|
|
1541
1811
|
score: matches.reduce((sum, match) => sum + match.score, 0)
|
|
1542
1812
|
};
|
|
1543
|
-
}).filter((result) => Boolean(result)).sort((a, b) => b.score - a.score || a.item.
|
|
1813
|
+
}).filter((result) => Boolean(result)).sort((a, b) => b.score - a.score || a.item.breadcrumb.localeCompare(b.item.breadcrumb));
|
|
1544
1814
|
};
|
|
1545
1815
|
const searchSurveyItems = (inputData, requestContext) => {
|
|
1546
1816
|
const input = inputSchema$4.parse(inputData);
|
|
@@ -1559,7 +1829,7 @@ const searchSurveyItems = (inputData, requestContext) => {
|
|
|
1559
1829
|
returned: page.items.length,
|
|
1560
1830
|
nextCursor: page.nextCursor,
|
|
1561
1831
|
truncated: page.truncated,
|
|
1562
|
-
ordering: "Results are ranked by descending text-match score, then
|
|
1832
|
+
ordering: "Results are ranked by descending text-match score, then structural breadcrumb. Matches within each result are ranked by descending score. Neither order is survey or respondent-visible order.",
|
|
1563
1833
|
results: page.items
|
|
1564
1834
|
}, "Survey-item search");
|
|
1565
1835
|
};
|
|
@@ -1596,14 +1866,30 @@ const inputSchema$3 = z.object({
|
|
|
1596
1866
|
"ui-context",
|
|
1597
1867
|
"history"
|
|
1598
1868
|
])).min(1).max(6)).describe("For current-context: context sections to inspect. Empty means omitted."),
|
|
1599
|
-
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
|
|
1600
|
-
|
|
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."),
|
|
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."),
|
|
1601
1878
|
targetLocale: optionalNonBlankString(40).describe("For localization: required target locale whose coverage should be inspected."),
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
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."),
|
|
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."),
|
|
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.`),
|
|
1607
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."),
|
|
1608
1894
|
includeChildren: optionalInput(z.boolean()).describe("For item-details: include direct survey-tree children."),
|
|
1609
1895
|
includeItemConfig: optionalInput(z.boolean()).describe("For item-details: include choice/form/internal item config details."),
|
|
@@ -1642,148 +1928,27 @@ const inspectionBackendFailure = (action, reason) => ({
|
|
|
1642
1928
|
modelSummary: reason
|
|
1643
1929
|
});
|
|
1644
1930
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1645
|
-
const MAX_LOCALIZATION_TEXT_CHARS = 2e3;
|
|
1646
|
-
const SURVEY_TRANSLATION_SURFACES = [
|
|
1647
|
-
"surveyCardContent",
|
|
1648
|
-
"navigationContent",
|
|
1649
|
-
"validationMessages"
|
|
1650
|
-
];
|
|
1651
|
-
const truncateLocalizationText = (content) => {
|
|
1652
|
-
const text = getContentPlainText(content).trim();
|
|
1653
|
-
return text.length > MAX_LOCALIZATION_TEXT_CHARS ? `${text.slice(0, MAX_LOCALIZATION_TEXT_CHARS)}...` : text;
|
|
1654
|
-
};
|
|
1655
|
-
const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
1656
|
-
const survey = snapshot.survey;
|
|
1657
|
-
const sourceLocale = input.sourceLocale ?? snapshot.ui.selectedLocale;
|
|
1658
|
-
const sourceTranslations = survey.translations?.[sourceLocale];
|
|
1659
|
-
const targetTranslations = survey.translations?.[targetLocale];
|
|
1660
|
-
if (!sourceTranslations) return {
|
|
1661
|
-
ok: false,
|
|
1662
|
-
reason: `Source locale "${sourceLocale}" is not present in the current survey draft.`
|
|
1663
|
-
};
|
|
1664
|
-
const surfaces = [];
|
|
1665
|
-
const addSurface = ({ contentKey, itemId, itemKey, itemType, sourceContent, surface, targetContent }) => {
|
|
1666
|
-
const sourceText = truncateLocalizationText(sourceContent);
|
|
1667
|
-
if (!sourceText) return;
|
|
1668
|
-
const sourcePath = `/translations/${encodeJsonPointerSegment(sourceLocale)}/${itemId ? `itemTranslations/${encodeJsonPointerSegment(itemId)}` : surface}/${encodeJsonPointerSegment(contentKey)}`;
|
|
1669
|
-
surfaces.push({
|
|
1670
|
-
scope: itemId ? "item" : "survey",
|
|
1671
|
-
...surface ? { surface } : {},
|
|
1672
|
-
...itemId ? {
|
|
1673
|
-
itemId,
|
|
1674
|
-
itemKey,
|
|
1675
|
-
itemType
|
|
1676
|
-
} : {},
|
|
1677
|
-
contentKey,
|
|
1678
|
-
sourceType: sourceContent.type,
|
|
1679
|
-
sourceText,
|
|
1680
|
-
sourceTextLength: getContentPlainText(sourceContent).trim().length,
|
|
1681
|
-
sourceTruncated: getContentPlainText(sourceContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS,
|
|
1682
|
-
sourcePath,
|
|
1683
|
-
...sourceContent.type !== "richText" ? { sourceTextPath: `${sourcePath}/content` } : {},
|
|
1684
|
-
...sourceContent.type === "richText" ? { sourceContent } : {},
|
|
1685
|
-
targetPresent: Boolean(targetContent && truncateLocalizationText(targetContent)),
|
|
1686
|
-
...targetContent ? {
|
|
1687
|
-
targetType: targetContent.type,
|
|
1688
|
-
targetText: truncateLocalizationText(targetContent),
|
|
1689
|
-
targetTextLength: getContentPlainText(targetContent).trim().length,
|
|
1690
|
-
targetTruncated: getContentPlainText(targetContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS
|
|
1691
|
-
} : {}
|
|
1692
|
-
});
|
|
1693
|
-
};
|
|
1694
|
-
for (const surface of SURVEY_TRANSLATION_SURFACES) {
|
|
1695
|
-
const targetSurface = targetTranslations?.[surface];
|
|
1696
|
-
for (const [contentKey, sourceContent] of Object.entries(sourceTranslations[surface] ?? {})) addSurface({
|
|
1697
|
-
surface,
|
|
1698
|
-
contentKey,
|
|
1699
|
-
sourceContent,
|
|
1700
|
-
targetContent: targetSurface?.[contentKey]
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
for (const item of survey.surveyItems) for (const [contentKey, sourceContent] of Object.entries(sourceTranslations.itemTranslations?.[item.id] ?? {})) addSurface({
|
|
1704
|
-
itemId: item.id,
|
|
1705
|
-
itemKey: item.key,
|
|
1706
|
-
itemType: item.itemType,
|
|
1707
|
-
contentKey,
|
|
1708
|
-
sourceContent,
|
|
1709
|
-
targetContent: targetTranslations?.itemTranslations?.[item.id]?.[contentKey]
|
|
1710
|
-
});
|
|
1711
|
-
const missingCount = surfaces.filter((surface) => !surface.targetPresent).length;
|
|
1712
|
-
const filteredSurfaces = input.missingOnly ? surfaces.filter((surface) => !surface.targetPresent) : surfaces;
|
|
1713
|
-
const parsedCursor = Number.parseInt(input.cursor ?? "0", 10);
|
|
1714
|
-
const offset = Number.isFinite(parsedCursor) && parsedCursor >= 0 ? parsedCursor : 0;
|
|
1715
|
-
const limit = input.limit ?? 20;
|
|
1716
|
-
const page = filteredSurfaces.slice(offset, offset + limit);
|
|
1717
|
-
const nextOffset = offset + page.length;
|
|
1718
|
-
return {
|
|
1719
|
-
ok: true,
|
|
1720
|
-
result: {
|
|
1721
|
-
sourceLocale,
|
|
1722
|
-
targetLocale,
|
|
1723
|
-
missingOnly: input.missingOnly ?? false,
|
|
1724
|
-
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.",
|
|
1725
|
-
totalSourceSurfaces: surfaces.length,
|
|
1726
|
-
totalAvailable: filteredSurfaces.length,
|
|
1727
|
-
missingCount,
|
|
1728
|
-
returned: page.length,
|
|
1729
|
-
...nextOffset < filteredSurfaces.length ? { nextCursor: String(nextOffset) } : {},
|
|
1730
|
-
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." },
|
|
1731
|
-
surfaces: page
|
|
1732
|
-
}
|
|
1733
|
-
};
|
|
1734
|
-
};
|
|
1735
1931
|
const resolveItemRef = (input, snapshot) => {
|
|
1736
1932
|
if (input.itemRef && input.itemRef !== "selected") return input.itemRef;
|
|
1737
1933
|
return snapshot?.ui.selectedItemId;
|
|
1738
1934
|
};
|
|
1739
|
-
const listAssetUsageSummaries = (survey) => {
|
|
1740
|
-
const usagesByAssetId = /* @__PURE__ */ new Map();
|
|
1741
|
-
const addContent = (content, usage) => {
|
|
1742
|
-
for (const assetUsage of getAssetUsagesFromContent(content)) {
|
|
1743
|
-
const usages = usagesByAssetId.get(assetUsage.assetId) ?? [];
|
|
1744
|
-
usages.push({
|
|
1745
|
-
...usage,
|
|
1746
|
-
blockIndex: assetUsage.blockIndex
|
|
1747
|
-
});
|
|
1748
|
-
usagesByAssetId.set(assetUsage.assetId, usages);
|
|
1749
|
-
}
|
|
1750
|
-
};
|
|
1751
|
-
for (const [locale, localeTranslations] of Object.entries(survey.translations ?? {})) {
|
|
1752
|
-
for (const scope of [
|
|
1753
|
-
"surveyCardContent",
|
|
1754
|
-
"navigationContent",
|
|
1755
|
-
"validationMessages"
|
|
1756
|
-
]) for (const [contentKey, content] of Object.entries(localeTranslations[scope] ?? {})) addContent(content, {
|
|
1757
|
-
locale,
|
|
1758
|
-
scope,
|
|
1759
|
-
contentKey
|
|
1760
|
-
});
|
|
1761
|
-
for (const [itemId, itemTranslations] of Object.entries(localeTranslations.itemTranslations ?? {})) for (const [contentKey, content] of Object.entries(itemTranslations ?? {})) addContent(content, {
|
|
1762
|
-
locale,
|
|
1763
|
-
scope: "itemTranslation",
|
|
1764
|
-
itemId,
|
|
1765
|
-
contentKey
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
return usagesByAssetId;
|
|
1769
|
-
};
|
|
1770
1935
|
const summarizeAssetSource = (asset) => ({
|
|
1771
1936
|
type: asset.source.type,
|
|
1772
1937
|
mediaType: asset.source.mediaType
|
|
1773
1938
|
});
|
|
1774
1939
|
const listAssets = (input, snapshot) => {
|
|
1775
1940
|
const survey = snapshot.survey;
|
|
1776
|
-
const
|
|
1941
|
+
const usages = snapshot.model.getAssetUsages();
|
|
1777
1942
|
const query = input.query?.trim().toLowerCase();
|
|
1778
1943
|
const allAssets = Object.entries(survey.assets ?? {}).map(([assetId, asset]) => {
|
|
1779
|
-
const
|
|
1944
|
+
const assetUsages = usages.filter((usage) => usage.assetId === assetId);
|
|
1780
1945
|
return {
|
|
1781
1946
|
assetId,
|
|
1782
1947
|
kind: asset.kind,
|
|
1783
1948
|
source: summarizeAssetSource(asset),
|
|
1784
1949
|
...asset.metadata ? { metadata: asset.metadata } : {},
|
|
1785
|
-
usageCount:
|
|
1786
|
-
usages
|
|
1950
|
+
usageCount: assetUsages.length,
|
|
1951
|
+
usages: assetUsages
|
|
1787
1952
|
};
|
|
1788
1953
|
}).filter((asset) => {
|
|
1789
1954
|
if (!query) return true;
|
|
@@ -1813,7 +1978,7 @@ const listAssets = (input, snapshot) => {
|
|
|
1813
1978
|
};
|
|
1814
1979
|
const surveyInspectTool = createTool({
|
|
1815
1980
|
id: TOOL_ID$3,
|
|
1816
|
-
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.",
|
|
1817
1982
|
inputSchema: inputSchema$3,
|
|
1818
1983
|
outputSchema: outputSchema$2,
|
|
1819
1984
|
mcp: { annotations: {
|
|
@@ -1862,7 +2027,10 @@ const surveyInspectTool = createTool({
|
|
|
1862
2027
|
const inspection = runInspectionBackend(() => searchSurveyItems({
|
|
1863
2028
|
query,
|
|
1864
2029
|
limit: input.limit,
|
|
1865
|
-
cursor: input.cursor
|
|
2030
|
+
cursor: input.cursor,
|
|
2031
|
+
itemTypes: input.itemTypes,
|
|
2032
|
+
elementTypes: input.elementTypes,
|
|
2033
|
+
locales: input.locales
|
|
1866
2034
|
}, requestContext));
|
|
1867
2035
|
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
1868
2036
|
const result = inspection.value;
|
|
@@ -1885,7 +2053,9 @@ const surveyInspectTool = createTool({
|
|
|
1885
2053
|
itemId,
|
|
1886
2054
|
includeSurveyChildren: input.includeChildren ?? false,
|
|
1887
2055
|
includeItemConfig: input.includeItemConfig ?? true,
|
|
1888
|
-
childrenLimit: input.limit
|
|
2056
|
+
childrenLimit: input.limit,
|
|
2057
|
+
responseVariablesLimit: input.limit,
|
|
2058
|
+
cursor: input.cursor
|
|
1889
2059
|
}, requestContext));
|
|
1890
2060
|
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
1891
2061
|
const result = inspection.value;
|
|
@@ -1897,32 +2067,36 @@ const surveyInspectTool = createTool({
|
|
|
1897
2067
|
};
|
|
1898
2068
|
}
|
|
1899
2069
|
if (input.action === "expression-diagnostics") {
|
|
2070
|
+
const visibilityIssues = getAssistantVisibilityIssues(createAssistantSurvey(snapshot.survey, snapshot.trustedCapabilities));
|
|
1900
2071
|
const allDiagnostics = analyzeSurveyAssistantExpressionReferences(snapshot.survey, snapshot.trustedCapabilities);
|
|
1901
2072
|
const itemId = input.itemRef ? resolveItemRef(input, snapshot) : void 0;
|
|
1902
2073
|
const diagnostics = itemId ? allDiagnostics.filter((diagnostic) => diagnostic.ownerItemId === itemId) : allDiagnostics;
|
|
1903
|
-
const orderedDiagnostics = [...diagnostics].sort((left, right) => Number(
|
|
1904
|
-
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;
|
|
1905
2076
|
const advisoryCount = diagnostics.length - blockingCount;
|
|
1906
2077
|
const limit = input.limit ?? 50;
|
|
1907
2078
|
const limitedDiagnostics = orderedDiagnostics.slice(0, limit);
|
|
1908
|
-
const
|
|
2079
|
+
const guidanceByCode = {};
|
|
1909
2080
|
for (const diagnostic of orderedDiagnostics) {
|
|
1910
|
-
const
|
|
1911
|
-
|
|
2081
|
+
const guidance = getSurveyAssistantExpressionDiagnosticGuidance(diagnostic);
|
|
2082
|
+
const existing = ownValue(guidanceByCode, diagnostic.code);
|
|
2083
|
+
setOwnValue(guidanceByCode, diagnostic.code, existing && !existing.includes(guidance) ? `${existing} ${guidance}` : guidance);
|
|
1912
2084
|
}
|
|
1913
2085
|
const result = {
|
|
1914
|
-
valid: blockingCount === 0,
|
|
2086
|
+
valid: blockingCount === 0 && visibilityIssues.length === 0,
|
|
2087
|
+
visibilityIssues,
|
|
2088
|
+
visibilityScope: "survey",
|
|
1915
2089
|
count: diagnostics.length,
|
|
1916
2090
|
blockingCount,
|
|
1917
2091
|
advisoryCount,
|
|
1918
2092
|
diagnostics: limitedDiagnostics,
|
|
1919
|
-
|
|
2093
|
+
guidanceByCode,
|
|
1920
2094
|
truncated: diagnostics.length > limit
|
|
1921
2095
|
};
|
|
1922
2096
|
return {
|
|
1923
2097
|
ok: true,
|
|
1924
2098
|
action: input.action,
|
|
1925
|
-
modelSummary: diagnostics.length === 0 ? "No expression reference diagnostics were found in the current survey draft." : blockingCount === 0 ? `${advisoryCount} advisory expression diagnostic${advisoryCount === 1 ? " was" : "s were"} found. Review each listed diagnostic according to its category-specific guidance.\n${stringifyForModel(result)}` : `${blockingCount} blocking expression reference diagnostic${blockingCount === 1 ? " was" : "s were"} found${advisoryCount > 0 ? ` alongside ${advisoryCount} advisory expression diagnostic${advisoryCount === 1 ? "" : "s"}` : ""}. Repair every blocking location, preserve advisories unless their category-specific guidance or the user requires a change, then run expression-diagnostics again before claiming the blocking repair is complete.\n${stringifyForModel(result)}`,
|
|
2099
|
+
modelSummary: visibilityIssues.length > 0 ? `Survey-wide visibility validation failed. Repair cyclic or callback-based visibility using independent controlling answers or serialized context values. These checks include ancestor, component and computed dependencies outside any item filter.\n${stringifyForModel(result)}` : diagnostics.length === 0 ? "No expression reference diagnostics were found in the current survey draft." : blockingCount === 0 ? `${advisoryCount} advisory expression diagnostic${advisoryCount === 1 ? " was" : "s were"} found. Review each listed diagnostic according to its category-specific guidance.\n${stringifyForModel(result)}` : `${blockingCount} blocking expression reference diagnostic${blockingCount === 1 ? " was" : "s were"} found${advisoryCount > 0 ? ` alongside ${advisoryCount} advisory expression diagnostic${advisoryCount === 1 ? "" : "s"}` : ""}. Repair every blocking location, preserve advisories unless their category-specific guidance or the user requires a change, then run expression-diagnostics again before claiming the blocking repair is complete.\n${stringifyForModel(result)}`,
|
|
1926
2100
|
result
|
|
1927
2101
|
};
|
|
1928
2102
|
}
|
|
@@ -1933,7 +2107,10 @@ const surveyInspectTool = createTool({
|
|
|
1933
2107
|
reason: "Localization inspection requires a targetLocale.",
|
|
1934
2108
|
modelSummary: "Localization inspection requires a targetLocale."
|
|
1935
2109
|
};
|
|
1936
|
-
const localization =
|
|
2110
|
+
const localization = inspectLocalization({
|
|
2111
|
+
...input,
|
|
2112
|
+
targetLocale: input.targetLocale
|
|
2113
|
+
}, snapshot);
|
|
1937
2114
|
if (!localization.ok) return {
|
|
1938
2115
|
ok: false,
|
|
1939
2116
|
action: input.action,
|
|
@@ -1975,7 +2152,7 @@ const surveyInspectTool = createTool({
|
|
|
1975
2152
|
inspectedPathCount: boundedPaths?.length ?? 0,
|
|
1976
2153
|
ignoredPathCount,
|
|
1977
2154
|
omittedPathCount,
|
|
1978
|
-
...omittedPathCount > 0 ? { nextPaths: requestedPaths?.slice(MAX_RAW_JSON_PATHS_PER_RESULT,
|
|
2155
|
+
...omittedPathCount > 0 ? { nextPaths: requestedPaths?.slice(MAX_RAW_JSON_PATHS_PER_RESULT, 40) } : {},
|
|
1979
2156
|
recovery: itemIdentifiers ? "Use a separate raw-json call without itemRef if the explicit paths are still needed." : "Retry the returned nextPaths in a separate raw-json call if they are still needed."
|
|
1980
2157
|
} : void 0;
|
|
1981
2158
|
const result = requestCoverage && isRecord(rawResult) ? {
|
|
@@ -1990,38 +2167,53 @@ const surveyInspectTool = createTool({
|
|
|
1990
2167
|
result
|
|
1991
2168
|
};
|
|
1992
2169
|
},
|
|
1993
|
-
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)
|
|
1994
2174
|
});
|
|
1995
2175
|
//#endregion
|
|
1996
2176
|
//#region src/server/tools/survey-attachment-tool.ts
|
|
1997
2177
|
const TOOL_ID$2 = "survey_attachment";
|
|
2178
|
+
const inputSchema$2 = z.object({
|
|
2179
|
+
documentIds: z.array(z.string().min(1)).min(1).max(4).optional().describe("Stored attachment IDs to load completely when no page/section focus is needed."),
|
|
2180
|
+
documentRequests: z.array(z.object({
|
|
2181
|
+
documentId: z.string().min(1),
|
|
2182
|
+
pages: z.object({
|
|
2183
|
+
start: z.number().int().min(1).max(1e5),
|
|
2184
|
+
end: z.number().int().min(1).max(1e5)
|
|
2185
|
+
}).optional().describe("One-based inclusive PDF page range to prioritize."),
|
|
2186
|
+
focus: z.string().min(1).max(300).optional().describe("Short section, question range, or fact to prioritize in the original file.")
|
|
2187
|
+
})).min(1).max(4).optional().describe("Stored attachments with an optional bounded reading focus.")
|
|
2188
|
+
});
|
|
2189
|
+
const outputSchema$1 = z.object({
|
|
2190
|
+
loaded: z.array(z.object({
|
|
2191
|
+
id: z.string(),
|
|
2192
|
+
filename: z.string(),
|
|
2193
|
+
mediaType: z.string(),
|
|
2194
|
+
pages: z.object({
|
|
2195
|
+
start: z.number().int(),
|
|
2196
|
+
end: z.number().int()
|
|
2197
|
+
}).optional(),
|
|
2198
|
+
focus: z.string().optional()
|
|
2199
|
+
})),
|
|
2200
|
+
unavailable: z.array(z.object({
|
|
2201
|
+
id: z.string(),
|
|
2202
|
+
filename: z.string().optional(),
|
|
2203
|
+
mediaType: z.string().optional(),
|
|
2204
|
+
reason: z.enum([
|
|
2205
|
+
"not-listed",
|
|
2206
|
+
"content-unavailable",
|
|
2207
|
+
"attachment-limit"
|
|
2208
|
+
])
|
|
2209
|
+
})).optional(),
|
|
2210
|
+
warnings: z.array(z.string()).optional()
|
|
2211
|
+
});
|
|
1998
2212
|
const surveyAttachmentTool = createTool({
|
|
1999
2213
|
id: TOOL_ID$2,
|
|
2000
2214
|
description: "Attach only the stored PDFs or images needed for the current task to the next native model request. For source continuation, use this before conversational recall and provide a page or section focus when known. The original file remains authoritative.",
|
|
2001
|
-
inputSchema:
|
|
2002
|
-
|
|
2003
|
-
documentRequests: z.array(z.object({
|
|
2004
|
-
documentId: z.string().min(1),
|
|
2005
|
-
pages: z.object({
|
|
2006
|
-
start: z.number().int().min(1).max(1e5),
|
|
2007
|
-
end: z.number().int().min(1).max(1e5)
|
|
2008
|
-
}).optional().describe("One-based inclusive PDF page range to prioritize."),
|
|
2009
|
-
focus: z.string().min(1).max(300).optional().describe("Short section, question range, or fact to prioritize in the original file.")
|
|
2010
|
-
})).min(1).max(4).optional().describe("Stored attachments with an optional bounded reading focus.")
|
|
2011
|
-
}),
|
|
2012
|
-
outputSchema: z.object({
|
|
2013
|
-
loaded: z.array(z.object({
|
|
2014
|
-
id: z.string(),
|
|
2015
|
-
filename: z.string(),
|
|
2016
|
-
mediaType: z.string(),
|
|
2017
|
-
pages: z.object({
|
|
2018
|
-
start: z.number().int(),
|
|
2019
|
-
end: z.number().int()
|
|
2020
|
-
}).optional(),
|
|
2021
|
-
focus: z.string().optional()
|
|
2022
|
-
})),
|
|
2023
|
-
warnings: z.array(z.string()).optional()
|
|
2024
|
-
}),
|
|
2215
|
+
inputSchema: inputSchema$2,
|
|
2216
|
+
outputSchema: outputSchema$1,
|
|
2025
2217
|
mcp: { annotations: {
|
|
2026
2218
|
title: "Load chat attachment",
|
|
2027
2219
|
readOnlyHint: true,
|
|
@@ -2030,8 +2222,8 @@ const surveyAttachmentTool = createTool({
|
|
|
2030
2222
|
openWorldHint: false
|
|
2031
2223
|
} },
|
|
2032
2224
|
execute: async (inputData, context) => {
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2225
|
+
const requestContext = context?.requestContext;
|
|
2226
|
+
const access = requestContext?.get("attachmentAccess");
|
|
2035
2227
|
const warnings = [];
|
|
2036
2228
|
const requests = /* @__PURE__ */ new Map();
|
|
2037
2229
|
for (const documentId of inputData.documentIds ?? []) requests.set(documentId, {});
|
|
@@ -2047,11 +2239,50 @@ const surveyAttachmentTool = createTool({
|
|
|
2047
2239
|
});
|
|
2048
2240
|
}
|
|
2049
2241
|
const requestedIds = [...requests.keys()];
|
|
2050
|
-
const authorized = access
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2242
|
+
const authorized = access?.documents.filter((document) => requests.has(document.id)) ?? [];
|
|
2243
|
+
const unavailable = requestedIds.filter((id) => !authorized.some((document) => document.id === id)).map((id) => ({
|
|
2244
|
+
id,
|
|
2245
|
+
reason: "not-listed"
|
|
2246
|
+
}));
|
|
2247
|
+
for (const { id } of unavailable) warnings.push(`Stored attachment ${id} is not in the available manifest.`);
|
|
2248
|
+
if (!access) return {
|
|
2249
|
+
loaded: [],
|
|
2250
|
+
unavailable,
|
|
2251
|
+
warnings
|
|
2252
|
+
};
|
|
2253
|
+
const attachments = await access.load(authorized.filter((document) => !access.selectedAttachments.some((attachment) => attachment.documentId === document.id)).map((document) => document.id));
|
|
2254
|
+
const selected = new Map(access.selectedAttachments.map((attachment) => [attachment.documentId, attachment]));
|
|
2255
|
+
const nativePayloads = new Map([...requestContext?.get("modelAttachments") ?? [], ...selected.values()].map((attachment) => [`${attachment.mediaType}\u0000${attachment.contentHash}`, Buffer.byteLength(attachment.data, "base64")]));
|
|
2256
|
+
let totalBytes = [...nativePayloads.values()].reduce((sum, bytes) => sum + bytes, 0);
|
|
2257
|
+
for (const document of authorized) {
|
|
2258
|
+
if (selected.has(document.id)) continue;
|
|
2259
|
+
const attachment = attachments.find((candidate) => candidate.documentId === document.id);
|
|
2260
|
+
let reason = "content-unavailable";
|
|
2261
|
+
if (attachment) {
|
|
2262
|
+
const key = `${attachment.mediaType}\u0000${attachment.contentHash}`;
|
|
2263
|
+
const bytes = Buffer.byteLength(attachment.data, "base64");
|
|
2264
|
+
const alreadyIncluded = nativePayloads.has(key);
|
|
2265
|
+
if (bytes <= access.limits.maxAttachmentBytes && (alreadyIncluded || nativePayloads.size < access.limits.maxAttachments && totalBytes + bytes <= access.limits.maxTotalAttachmentBytes)) {
|
|
2266
|
+
selected.set(document.id, attachment);
|
|
2267
|
+
if (!alreadyIncluded) {
|
|
2268
|
+
nativePayloads.set(key, bytes);
|
|
2269
|
+
totalBytes += bytes;
|
|
2270
|
+
}
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
reason = "attachment-limit";
|
|
2274
|
+
}
|
|
2275
|
+
unavailable.push({
|
|
2276
|
+
id: document.id,
|
|
2277
|
+
filename: document.name,
|
|
2278
|
+
mediaType: document.mediaType,
|
|
2279
|
+
reason
|
|
2280
|
+
});
|
|
2281
|
+
warnings.push(reason === "attachment-limit" ? `Stored attachment ${document.name} (${document.id}) exceeds this turn's attachment limit and was not loaded. Use a new turn with fewer or smaller sources.` : `Stored attachment ${document.name} (${document.id}) is unavailable. No content was loaded for it. Retry loading it or ask the user to upload it again.`);
|
|
2282
|
+
}
|
|
2283
|
+
access.selectedAttachments.splice(0, access.selectedAttachments.length, ...selected.values());
|
|
2053
2284
|
return {
|
|
2054
|
-
loaded: authorized.map((document) => {
|
|
2285
|
+
loaded: authorized.filter((document) => selected.has(document.id)).map((document) => {
|
|
2055
2286
|
const request = requests.get(document.id);
|
|
2056
2287
|
const pages = document.mediaType === "application/pdf" ? request?.pages : void 0;
|
|
2057
2288
|
if (request?.pages && !pages) warnings.push(`Ignored a PDF page focus for non-PDF attachment ${document.name}.`);
|
|
@@ -2063,6 +2294,7 @@ const surveyAttachmentTool = createTool({
|
|
|
2063
2294
|
...request?.focus ? { focus: request.focus } : {}
|
|
2064
2295
|
};
|
|
2065
2296
|
}),
|
|
2297
|
+
...unavailable.length > 0 ? { unavailable } : {},
|
|
2066
2298
|
...warnings.length > 0 ? { warnings } : {}
|
|
2067
2299
|
};
|
|
2068
2300
|
},
|
|
@@ -2135,14 +2367,14 @@ const formatAuditForModel = (output) => {
|
|
|
2135
2367
|
const location = [
|
|
2136
2368
|
finding.itemId,
|
|
2137
2369
|
finding.locale,
|
|
2138
|
-
finding.
|
|
2370
|
+
finding.localizationRefs ? JSON.stringify(finding.localizationRefs) : finding.path?.join("/")
|
|
2139
2371
|
].filter(Boolean).join(":");
|
|
2140
2372
|
return `- [${finding.severity}] ${finding.code}${location ? ` (${location})` : ""}: ${previewAuditMessage(finding.message)}${finding.suggestion ? ` Guidance: ${previewAuditMessage(finding.suggestion)}` : ""}`;
|
|
2141
2373
|
}).join("\n")}` : void 0,
|
|
2142
2374
|
report.qualityReviewPrinciples.length > 0 ? `Qualitative review rubric:\n${report.qualityReviewPrinciples.map((principle) => `- ${principle.id}: ${principle.prompt}`).join("\n")}` : void 0,
|
|
2143
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) => {
|
|
2144
2376
|
const content = entry.content ? `; current content=${JSON.stringify(previewAuditMessage(entry.content))}` : "; current content unavailable";
|
|
2145
|
-
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}`;
|
|
2146
2378
|
}).join("\n")}\n${report.keyReview.note}` : `Key/content review evidence: none returned (${report.keyReview.entryCount} total entries).`,
|
|
2147
2379
|
report.note,
|
|
2148
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."
|
|
@@ -2193,21 +2425,7 @@ const inputSchema = z.object({
|
|
|
2193
2425
|
const slug = (value) => value.toLowerCase().replace(/\.[^.]+$/, "").replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100) || "image";
|
|
2194
2426
|
const OMITTED_EMBEDDED_IMAGE_DATA = "[embedded image data omitted from transcript]";
|
|
2195
2427
|
const getAssetReferenceLabels = (snapshot, assetId) => {
|
|
2196
|
-
|
|
2197
|
-
if (!translations) return [];
|
|
2198
|
-
const references = [];
|
|
2199
|
-
const inspectContent = (content, label) => {
|
|
2200
|
-
if (getAssetUsagesFromContent(content).some((usage) => usage.assetId === assetId)) references.push(label);
|
|
2201
|
-
};
|
|
2202
|
-
for (const [locale, localeTranslations] of Object.entries(translations)) {
|
|
2203
|
-
for (const surface of [
|
|
2204
|
-
"surveyCardContent",
|
|
2205
|
-
"navigationContent",
|
|
2206
|
-
"validationMessages"
|
|
2207
|
-
]) for (const [contentKey, content] of Object.entries(localeTranslations[surface] ?? {})) inspectContent(content, `${locale}:${surface}:${contentKey}`);
|
|
2208
|
-
for (const [itemId, itemTranslations] of Object.entries(localeTranslations.itemTranslations ?? {})) for (const [contentKey, content] of Object.entries(itemTranslations ?? {})) inspectContent(content, `${locale}:item:${itemId}:${contentKey}`);
|
|
2209
|
-
}
|
|
2210
|
-
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}`}`) ?? [];
|
|
2211
2429
|
};
|
|
2212
2430
|
const getToolOutput = (value) => value && typeof value === "object" && "output" in value && value.output !== void 0 ? value.output : value;
|
|
2213
2431
|
const describeAssetOperation = (operation) => {
|
|
@@ -2227,8 +2445,8 @@ const summarizeAssetChange = (event) => {
|
|
|
2227
2445
|
lines.push(`Imported: ${event.operations.filter((operation) => operation.kind === "set-image-asset").map(describeAssetOperation).join(", ")}`);
|
|
2228
2446
|
lines.push(`Asset id to reference: ${importedAssetIds.join(", ")}`);
|
|
2229
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.");
|
|
2230
|
-
lines.push(`
|
|
2231
|
-
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>" }] }.`);
|
|
2232
2450
|
}
|
|
2233
2451
|
}
|
|
2234
2452
|
if (event.validation.issues.length > 0) lines.push(`Issues:\n${event.validation.issues.map((issue) => `- ${issue.code}: ${issue.message}`).join("\n")}`);
|
|
@@ -2277,13 +2495,13 @@ const surveyAssetTool = createTool({
|
|
|
2277
2495
|
const manifest = access?.documents.find((entry) => entry.id === input.documentId);
|
|
2278
2496
|
if (!access || !manifest) throw new Error(`Attachment "${input.documentId}" is not available for this turn.`);
|
|
2279
2497
|
if (!manifest.mediaType.startsWith("image/")) throw new Error(`Attachment "${manifest.name}" is not an image.`);
|
|
2280
|
-
const
|
|
2498
|
+
const loaded = (await access.load([input.documentId])).find((document) => document.documentId === input.documentId);
|
|
2281
2499
|
if (!loaded) throw new Error(`Attachment "${manifest.name}" could not be loaded.`);
|
|
2282
2500
|
const base = input.assetId ?? slug(manifest.name);
|
|
2283
2501
|
let assetId = base;
|
|
2284
2502
|
let suffix = 2;
|
|
2285
2503
|
const stagedAssetIds = new Set(turnDraft.draftOperations.flatMap((draftOperation) => draftOperation.kind === "set-image-asset" ? [draftOperation.assetId] : []));
|
|
2286
|
-
while (snapshot?.survey.assets
|
|
2504
|
+
while (ownValue(snapshot?.survey.assets, assetId) || stagedAssetIds.has(assetId)) assetId = `${base}-${suffix++}`;
|
|
2287
2505
|
operation = {
|
|
2288
2506
|
kind: "set-image-asset",
|
|
2289
2507
|
assetId,
|
|
@@ -2334,7 +2552,7 @@ const surveyAssetTool = createTool({
|
|
|
2334
2552
|
validation: {
|
|
2335
2553
|
...event.validation,
|
|
2336
2554
|
valid: false,
|
|
2337
|
-
issues: [...event.validation.issues, {
|
|
2555
|
+
issues: [...event.validation.issues.filter((issue) => issue.code !== "image-asset-still-referenced"), {
|
|
2338
2556
|
severity: "error",
|
|
2339
2557
|
code: "image-asset-still-referenced",
|
|
2340
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.`,
|
|
@@ -2378,4 +2596,4 @@ const surveyAssistantTools = Object.freeze({
|
|
|
2378
2596
|
//#endregion
|
|
2379
2597
|
export { surveyAuditTool as i, surveyAssistantTools as n, surveyAssetTool as r, SURVEY_ASSISTANT_TOOL_IDS as t };
|
|
2380
2598
|
|
|
2381
|
-
//# sourceMappingURL=tools-
|
|
2599
|
+
//# sourceMappingURL=tools-D0KBDolR.mjs.map
|