@case-framework/survey-assistant 0.6.0 → 0.7.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 +9 -4
- package/dist/{authoring-references-CW38pYzK.mjs → authoring-references-BJQ_1nMX.mjs} +2 -2
- package/dist/authoring-references-BJQ_1nMX.mjs.map +1 -0
- package/dist/capabilities-1KNnNO1_.mjs +73 -0
- package/dist/capabilities-1KNnNO1_.mjs.map +1 -0
- package/dist/capabilities-default.d.mts +1 -1
- package/dist/capabilities-default.d.mts.map +1 -1
- package/dist/capabilities-default.mjs +22 -25
- package/dist/capabilities-default.mjs.map +1 -1
- package/dist/{constants-BMGTgfjv.d.mts → constants-Be91Gay9.d.mts} +3 -3
- package/dist/{constants-BMGTgfjv.d.mts.map → constants-Be91Gay9.d.mts.map} +1 -1
- package/dist/{controller-proxy-DMTqer6T.mjs → controller-proxy-BH05e-C2.mjs} +21 -8
- package/dist/controller-proxy-BH05e-C2.mjs.map +1 -0
- package/dist/{controller-proxy-mRvez2qw.d.mts → controller-proxy-Bd0ZHBPF.d.mts} +11 -11
- package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +1 -0
- package/dist/{engine-CQwOAcwK.mjs → engine-Bo85Oyj0.mjs} +2985 -2347
- package/dist/engine-Bo85Oyj0.mjs.map +1 -0
- package/dist/engine.d.mts +2 -2
- package/dist/engine.mjs +3 -3
- package/dist/{index-D6nqkz0O.d.mts → index-Tnqv-yKW.d.mts} +56 -71
- package/dist/index-Tnqv-yKW.d.mts.map +1 -0
- package/dist/index-l_EK4yK7.d.mts +404 -0
- package/dist/index-l_EK4yK7.d.mts.map +1 -0
- package/dist/{index-DvTqML-0.d.mts → index-nhrgbgO5.d.mts} +19 -5
- package/dist/index-nhrgbgO5.d.mts.map +1 -0
- package/dist/{index-B0lxsAzx.d.mts → index-x0qprHgK.d.mts} +19 -7
- package/dist/index-x0qprHgK.d.mts.map +1 -0
- package/dist/{lifecycle-BmGAIUlv.d.mts → lifecycle-Bx0aq_4S.d.mts} +7 -1
- package/dist/{lifecycle-BmGAIUlv.d.mts.map → lifecycle-Bx0aq_4S.d.mts.map} +1 -1
- package/dist/{memory-thread-repository-CjFGVNZh.mjs → memory-thread-repository-KWHQWFEW.mjs} +43 -12
- package/dist/memory-thread-repository-KWHQWFEW.mjs.map +1 -0
- package/dist/{protocol-DJUP4gc0.mjs → protocol-BhzQbx81.mjs} +66 -16
- package/dist/protocol-BhzQbx81.mjs.map +1 -0
- package/dist/protocol.d.mts +3 -3
- package/dist/protocol.mjs +2 -2
- package/dist/react-B3ybjJ-X.mjs +1620 -0
- package/dist/react-B3ybjJ-X.mjs.map +1 -0
- package/dist/react-integration.d.mts +1 -1
- package/dist/react-integration.mjs +1 -1
- package/dist/react.d.mts +3 -3
- package/dist/react.mjs +3 -3
- package/dist/references/assistant-operations.md +45 -39
- package/dist/references/expressions.md +28 -0
- package/dist/references/item-types.md +1 -1
- package/dist/references/localization.md +5 -4
- package/dist/references/rich-text-content.md +60 -27
- package/dist/references/source-material-surveys.md +9 -2
- package/dist/references/survey-data-model.md +9 -1
- package/dist/{request-context-BjTHNcDd.mjs → request-context-CG4SWijt.mjs} +3 -3
- package/dist/{request-context-BjTHNcDd.mjs.map → request-context-CG4SWijt.mjs.map} +1 -1
- package/dist/server-agent.d.mts +19 -8
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +7 -11
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +19 -13
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +7 -2
- package/dist/server-runtime.mjs.map +1 -1
- package/dist/server-tasks.d.mts +1 -1
- package/dist/server-tasks.mjs +2 -2
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +5 -5
- package/dist/server.mjs +3 -3
- package/dist/{tasks-DxzD25Zc.d.mts → tasks-CYqrcaRO.d.mts} +4 -4
- package/dist/{tasks-DxzD25Zc.d.mts.map → tasks-CYqrcaRO.d.mts.map} +1 -1
- package/dist/{thread-handlers-AbQbt4sX.d.mts → thread-handlers-DBAaU7BA.d.mts} +6 -4
- package/dist/thread-handlers-DBAaU7BA.d.mts.map +1 -0
- package/dist/{tools-CFh35R4d.mjs → tools-BvSovehT.mjs} +431 -495
- package/dist/tools-BvSovehT.mjs.map +1 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.mts +5 -4
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +619 -66
- package/dist/ui.mjs.map +1 -1
- package/docs/integration.md +40 -0
- package/package.json +21 -19
- package/dist/authoring-references-CW38pYzK.mjs.map +0 -1
- package/dist/controller-proxy-DMTqer6T.mjs.map +0 -1
- package/dist/controller-proxy-mRvez2qw.d.mts.map +0 -1
- package/dist/engine-CQwOAcwK.mjs.map +0 -1
- package/dist/form-authoring-kQDgC5oz.mjs +0 -155
- package/dist/form-authoring-kQDgC5oz.mjs.map +0 -1
- package/dist/index-B0lxsAzx.d.mts.map +0 -1
- package/dist/index-BS-Xjk1h.d.mts +0 -224
- package/dist/index-BS-Xjk1h.d.mts.map +0 -1
- package/dist/index-D6nqkz0O.d.mts.map +0 -1
- package/dist/index-DvTqML-0.d.mts.map +0 -1
- package/dist/memory-thread-repository-CjFGVNZh.mjs.map +0 -1
- package/dist/protocol-DJUP4gc0.mjs.map +0 -1
- package/dist/react-Dqlmasrs.mjs +0 -768
- package/dist/react-Dqlmasrs.mjs.map +0 -1
- package/dist/thread-handlers-AbQbt4sX.d.mts.map +0 -1
- package/dist/tools-CFh35R4d.mjs.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { X as SURVEY_OPERATION_KINDS, bt as surveyOperationSchemasByKind, f as surveyChangeSetSchema, q as assistantItemTypeManifestSchema, yt as surveyOperationSchema } from "./protocol-
|
|
2
|
-
import {
|
|
3
|
-
import { A as
|
|
4
|
-
import { r as getSurveyAssistantEngineContext, s as requireSurveyAssistantTurnState } from "./request-context-
|
|
5
|
-
import { a as surveyAuthoringReferences, i as surveyAuthoringReferenceTopics, r as getBuiltInItemTypeAuthoringReference, t as builtInSurveyAuthoringItemTypes } from "./authoring-references-
|
|
1
|
+
import { X as SURVEY_OPERATION_KINDS, bt as surveyOperationSchemasByKind, f as surveyChangeSetSchema, q as assistantItemTypeManifestSchema, yt as surveyOperationSchema } from "./protocol-BhzQbx81.mjs";
|
|
2
|
+
import { a as getDeterministicCreateItemTypes, o as inspectAssistantItem, s as resolveAssistantCapabilityReference } from "./capabilities-1KNnNO1_.mjs";
|
|
3
|
+
import { $ as sliceWithCursor, A as buildEmbeddedFormDetails, C as buildExpressionMutationBatch, F as clampLimit, G as itemMatchesQuery, H as getRawSurvey, I as findDirectChildrenByFullKey, J as mapOutlineById, K as itemSummarySchema, M as buildItemTranslationEntries, Q as resolveItemSummary, S as normalizeSurveyChangeOperations, T as expressionMutationSchema, U as getRawSurveyItems, V as getKeyIndexEntries, W as itemConfigDetailsSchema, X as normalizeText, Y as mapRawItemsById, at as summarizeRawItem, ct as analyzeSurveyAssistantExpressionReferences, d as collectExpressionMutationRecoveryTargets, et as summarizeFocusItemNode, f as collectMalformedSurveyOperationRecoveryTargets, it as summarizeOutlineNode, j as buildItemConfigDetails, k as buildConfigSearchText, m as auditSurvey, nt as summarizeKeyIndexEntries, ot as translationEntrySchema, p as collectSurveyOperationRecoveryTargets, q as mapKeyIndexById, st as unique, tt as summarizeItemById, u as collectExpressionMutationRecoveryRefinements, ut as getSurveyAssistantExpressionDiagnosticGuidance, v as createSurveyChangeSet } from "./engine-Bo85Oyj0.mjs";
|
|
4
|
+
import { r as getSurveyAssistantEngineContext, s as requireSurveyAssistantTurnState } from "./request-context-CG4SWijt.mjs";
|
|
5
|
+
import { a as surveyAuthoringReferences, i as surveyAuthoringReferenceTopics, r as getBuiltInItemTypeAuthoringReference, t as builtInSurveyAuthoringItemTypes } from "./authoring-references-BJQ_1nMX.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { JsonPatchError, SURVEY_EDITOR_ITEM_COLORS, encodeJsonPointerSegment, getAssetUsagesFromContent, getContentPlainText, getValueAtJsonPointer } from "@case-framework/survey-core";
|
|
7
|
+
import { JsonPatchError, SURVEY_EDITOR_ITEM_COLORS, encodeJsonPointerSegment, getAssetUsagesFromContent, getContentPlainText, getValueAtJsonPointer, isBlockingSurveyExpressionReferenceDiagnostic } from "@case-framework/survey-core";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { createTool, isValidationError } from "@mastra/core/tools";
|
|
10
10
|
//#region src/server/operation-contract-reference.ts
|
|
@@ -53,12 +53,17 @@ const formatItemCapabilityProfile = (manifest, kind) => {
|
|
|
53
53
|
"",
|
|
54
54
|
`${manifest.displayName}: ${manifest.description ?? "No additional description."}`,
|
|
55
55
|
`Creation mode: ${manifest.create}.`,
|
|
56
|
+
`Supported operations: ${manifest.supportedOperations.join(", ")}.`,
|
|
57
|
+
manifest.authoring?.compactHint,
|
|
56
58
|
manifest.authoring?.configSummary ? `Configuration: ${manifest.authoring.configSummary}` : void 0,
|
|
57
59
|
manifest.creationPresets?.length ? `Available creation preset ids: ${manifest.creationPresets.map((preset) => `\`${preset.id}\``).join(", ")}. Use the server-trusted configuration guidance above for their semantics.` : void 0,
|
|
58
|
-
fixedSurfaces.length > 0 ? `Fixed content surfaces: ${fixedSurfaces.map((surface) => surface.key).join(", ")}.` : "Fixed content surfaces: none.",
|
|
59
|
-
patternedSurfaces.length > 0 ? `Dynamic content surfaces: ${patternedSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
|
|
60
|
-
kind === "create-item" && requiredSurfaces.length > 0 ? `Required on creation: ${requiredSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
|
|
61
|
-
manifest.referenceTopics?.length ? `Load
|
|
60
|
+
fixedSurfaces.length > 0 ? `Fixed content surfaces: ${fixedSurfaces.map((surface) => `${surface.key} (format=${surface.format})`).join(", ")}.` : "Fixed content surfaces: none.",
|
|
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({
|
|
64
|
+
topic,
|
|
65
|
+
itemType: manifest.itemType
|
|
66
|
+
})).join("; ")}.` : void 0
|
|
62
67
|
].filter((line) => Boolean(line));
|
|
63
68
|
if (kind === "create-item") lines.push("", "Creation scope: include the initial structure, configuration, and visible translations that are already known. A later successful mutation call in the same turn may target the created id; the server adds that delta to the validated turn draft.");
|
|
64
69
|
return lines.join("\n");
|
|
@@ -76,9 +81,9 @@ const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
|
76
81
|
`# \`${kind}\` operation contract`,
|
|
77
82
|
"",
|
|
78
83
|
"This JSON Schema is generated from the structural validator used by `survey_change`; it is",
|
|
79
|
-
"the canonical field shape. Cross-field and snapshot-dependent rules are
|
|
80
|
-
"proposal is validated.
|
|
81
|
-
"canonical
|
|
84
|
+
"the canonical post-normalization field shape. Cross-field and snapshot-dependent rules are",
|
|
85
|
+
"enforced when the proposal is validated. Follow focused authoring guidance when it recommends",
|
|
86
|
+
"a supported model-facing shorthand; otherwise use this canonical shape.",
|
|
82
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, form, or item-type reference for authoring guidance." : "This full view is intended for protocol debugging and may be large.",
|
|
83
88
|
"",
|
|
84
89
|
"```json",
|
|
@@ -139,7 +144,7 @@ const suggestCanonicalNames = (input, candidates, limit = 3) => [...new Set(cand
|
|
|
139
144
|
//#endregion
|
|
140
145
|
//#region src/server/tools/tool-model-output.ts
|
|
141
146
|
const FALLBACK_MODEL_TEXT = "The tool returned no model-readable output. Review the tool arguments and retry with valid values.";
|
|
142
|
-
const unwrapToolOutput
|
|
147
|
+
const unwrapToolOutput = (value) => value && typeof value === "object" && "output" in value && value.output !== void 0 ? value.output : value;
|
|
143
148
|
const createTextModelOutput = (text) => ({
|
|
144
149
|
type: "content",
|
|
145
150
|
value: [{
|
|
@@ -148,7 +153,7 @@ const createTextModelOutput = (text) => ({
|
|
|
148
153
|
}]
|
|
149
154
|
});
|
|
150
155
|
const toTextModelOutput = (value, summarize) => {
|
|
151
|
-
const output = unwrapToolOutput
|
|
156
|
+
const output = unwrapToolOutput(value);
|
|
152
157
|
if (isValidationError(output)) return createTextModelOutput(output.message);
|
|
153
158
|
try {
|
|
154
159
|
return createTextModelOutput(summarize(output));
|
|
@@ -158,7 +163,7 @@ const toTextModelOutput = (value, summarize) => {
|
|
|
158
163
|
};
|
|
159
164
|
//#endregion
|
|
160
165
|
//#region src/server/tools/survey-authoring-reference-tool.ts
|
|
161
|
-
const TOOL_ID$
|
|
166
|
+
const TOOL_ID$6 = "survey_authoring_reference";
|
|
162
167
|
const topicSchema = z.string().min(1).max(120);
|
|
163
168
|
const inputSchema$10 = z.object({
|
|
164
169
|
topic: topicSchema.describe("The narrow authoring reference to load."),
|
|
@@ -181,7 +186,7 @@ const formatCanonicalNameRecovery = (input, candidates, noun) => {
|
|
|
181
186
|
return suggestions.length > 0 ? ` Closest canonical ${noun}${suggestions.length === 1 ? "" : "s"}: ${suggestions.map((value) => `"${value}"`).join(", ")}. Retry with the intended canonical name.` : " Retry without the focused name to load the available catalog.";
|
|
182
187
|
};
|
|
183
188
|
const surveyAuthoringReferenceTool = createTool({
|
|
184
|
-
id: TOOL_ID$
|
|
189
|
+
id: TOOL_ID$6,
|
|
185
190
|
description: "Load one narrow CASE authoring reference. With topic \"item-types\", pass itemType to load only that type. For a survey_change operation, use topic \"assistant-operations\" with operationKind and optionally itemType to compose the structural contract with that installed item capability. Compact detail is the authoring default; full detail is only for protocol debugging.",
|
|
186
191
|
inputSchema: inputSchema$10,
|
|
187
192
|
outputSchema: outputSchema$8,
|
|
@@ -264,14 +269,27 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
264
269
|
title: capabilityReference.title ?? `${capability?.manifest.displayName} authoring`,
|
|
265
270
|
content: capabilityReference.markdown
|
|
266
271
|
} : void 0;
|
|
267
|
-
const
|
|
272
|
+
const focusedCapabilityProfile = inputData.topic === "item-types" && itemCapability ? {
|
|
273
|
+
title: itemCapability.displayName,
|
|
274
|
+
content: formatItemCapabilityProfile(itemCapability)
|
|
275
|
+
} : void 0;
|
|
276
|
+
const baseEntry = inputData.topic === "item-types" && inputData.itemType ? focusedBuiltInItemTypeReference ?? focusedCapabilityProfile : builtInEntry;
|
|
277
|
+
const entry = baseEntry && capabilityEntry ? {
|
|
278
|
+
title: baseEntry.title,
|
|
279
|
+
content: baseEntry.content === capabilityEntry.content ? baseEntry.content : [
|
|
280
|
+
baseEntry.content,
|
|
281
|
+
"## Item-specific constraints",
|
|
282
|
+
"Apply the following capability constraints when using the shared grammar above.",
|
|
283
|
+
capabilityEntry.content
|
|
284
|
+
].join("\n\n")
|
|
285
|
+
} : baseEntry ?? capabilityEntry;
|
|
268
286
|
if (!entry) return {
|
|
269
287
|
ok: false,
|
|
270
288
|
topic: inputData.topic,
|
|
271
289
|
itemType: inputData.itemType,
|
|
272
290
|
operationKind: inputData.operationKind,
|
|
273
291
|
title: "Reference unavailable",
|
|
274
|
-
content: (inputData.itemType ? `No reference named "${inputData.topic}" is installed for item type "${inputData.itemType}".` : `No built-in reference named "${inputData.topic}" is available.`) + formatCanonicalNameRecovery(inputData.topic, surveyAuthoringReferenceTopics, "reference")
|
|
292
|
+
content: (inputData.itemType ? `No reference named "${inputData.topic}" is installed for item type "${inputData.itemType}".` : `No built-in reference named "${inputData.topic}" is available.`) + formatCanonicalNameRecovery(inputData.topic, [...surveyAuthoringReferenceTopics, ...itemCapability?.referenceTopics ?? []], "reference")
|
|
275
293
|
};
|
|
276
294
|
return {
|
|
277
295
|
ok: true,
|
|
@@ -285,29 +303,44 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
285
303
|
toModelOutput: (value) => toTextModelOutput(value, (output) => `# ${output.title}\n\n${output.content}`)
|
|
286
304
|
});
|
|
287
305
|
//#endregion
|
|
306
|
+
//#region src/server/tools/mutation-recovery.ts
|
|
307
|
+
const retryOfChangeIdsSchema = z.array(z.string().min(1)).max(20).optional().describe("Rejected ids for required retries or intentional removal of pending targets. Retry the complete corrected scope with non-empty mutations. Only allowsAcknowledgment permits inspected no-ops via survey_change operations: []. Linkage never clears identifiable pending edits.");
|
|
308
|
+
const summarizePendingRepairs = (event) => {
|
|
309
|
+
if (!event.pendingRepairs?.length) return [];
|
|
310
|
+
const requiredIds = event.pendingRepairs.filter((repair) => repair.requiresExplicitRetry).map((repair) => repair.changeId);
|
|
311
|
+
return [
|
|
312
|
+
...requiredIds.length > 0 ? [`Required retry linkage (copy exactly): ${JSON.stringify({ retryOfChangeIds: requiredIds })}. This addresses linkage only; preserve the requested content and structure while correcting validation errors.`] : [],
|
|
313
|
+
"TURN APPLY STATE: BLOCKED. These rejected proposal obligations remain unapplied. Repair every target; successful partial retries accumulate but do not clear the remaining obligations.",
|
|
314
|
+
...event.pendingRepairs.map((repair) => `Rejected change ${repair.changeId}: ${JSON.stringify(repair.targets)}.${repair.requiresExplicitRetry ? ` This repair requires explicit linkage. Include every id in the aggregate Required retry linkage above with the complete corrected scope. ${repair.allowsAcknowledgment ? "Validator-confirmed no-op obligations may be acknowledged after inspection with survey_change operations: []; other obligations remain pending." : "A non-empty corrected retry is required; an empty acknowledgment cannot resolve this rejection."} Identifiable targets still need repair. No further mutations are staged until required links are supplied.` : " Retry these same operations and surfaces with corrected values. If intentionally removing a pending target, include its rejected change id in retryOfChangeIds."}`)
|
|
315
|
+
];
|
|
316
|
+
};
|
|
317
|
+
//#endregion
|
|
288
318
|
//#region src/server/tools/survey-change-tool.ts
|
|
289
|
-
const TOOL_ID$
|
|
319
|
+
const TOOL_ID$5 = "survey_change";
|
|
290
320
|
const OPERATIONS_DESCRIPTION = [
|
|
291
321
|
"Typed survey operation objects to validate and package as one undoable change. Every object uses discriminator \"kind\", never \"type\".",
|
|
292
322
|
"For an unfamiliar canonical shape, load survey_authoring_reference with topic \"assistant-operations\" and operationKind; it returns only that operation schema, generated from the validator.",
|
|
293
323
|
"Any translations owned by an operation must be inside that same operation object. Never put translations beside the survey_change operations array.",
|
|
294
|
-
"
|
|
295
|
-
"
|
|
324
|
+
"Every translation payload uses exactly one representation: plainText, canonical content, or the model-facing richTextBlocks shorthand. Shorthand is accepted wherever a typed translation accepts structured content and is normalized to canonical content before validation. Use it only for surfaces that focused guidance identifies as rendering rich text.",
|
|
325
|
+
"Canonical create-item shape: put parentItemId and optional index in target; put the required key and itemType, plus optional id, config, and creation-time expressions, in item; put respondent-visible plainText or structured content in top-level translations. Put choice maxSelection and options inside item.config. For top-level creation, target the root group identified by the snapshot or inspection.",
|
|
326
|
+
"Use update-item-translation with itemId, locale, contentKey, and plainText for ordinary respondent-visible text. For a complete formatted replacement, prefer the richTextBlocks shorthand from rich-text-content; canonical content remains available when needed. Item title, subtitle, topContent, bottomContent, cardFooter, info content, option labels, and field labels are translations, not item labels.",
|
|
296
327
|
"Use add-survey-locale only when the user explicitly requests an empty language registration. An ordinary localization request requires real translation operations, which introduce the locale themselves.",
|
|
297
328
|
"Use remove-survey-locale with locale to destructively remove an existing survey language and all translations stored for it.",
|
|
298
329
|
"Each call adds a delta to this turn's validated draft. Later calls may target ids created by earlier successful calls in the same turn.",
|
|
330
|
+
"Use operations: [] only with retryOfChangeIds when pendingRepairs.allowsAcknowledgment is true, after inspecting the validator-confirmed no-op. This does not discharge identifiable pending edits or create an editor commit by itself.",
|
|
299
331
|
"For an existing choice item, use add-choice-option with itemId, optional locale/index, and option: { id?, key?, label }; label is a plain string and creates its translation.",
|
|
300
332
|
"Every reorder replaces the complete inspected list: reorder-choice-options uses optionIds, reorder-form-field-groups uses groupIds, and reorder-items uses itemIds for every direct child. Include every existing entry exactly once in desired order.",
|
|
301
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.`}`,
|
|
302
334
|
"Use patch-item-config with itemId and plural patches whose JSON Pointer paths are relative to that item config root.",
|
|
303
|
-
"Use update-placed-image only for an image
|
|
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.",
|
|
304
336
|
"When deleting referenced images and assets, include every reference-removal operation and remove-image-asset last in the same operations array.",
|
|
305
|
-
"
|
|
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.",
|
|
306
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."
|
|
307
339
|
].join(" ");
|
|
308
340
|
const inputSchema$9 = z.object({
|
|
341
|
+
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
309
342
|
summary: z.string().min(1).max(500).describe("Short user-facing summary of the intended survey change."),
|
|
310
|
-
operations: z.array(z.unknown()).min(
|
|
343
|
+
operations: z.array(z.unknown()).min(0).max(200).describe(OPERATIONS_DESCRIPTION)
|
|
311
344
|
}).strict();
|
|
312
345
|
const rejectedOperationParseEvent = ({ issueText, summary, turnId }) => ({
|
|
313
346
|
protocolVersion: 1,
|
|
@@ -413,10 +446,11 @@ const summarizeValidation = (event) => {
|
|
|
413
446
|
lines.push("Warnings:");
|
|
414
447
|
for (const warning of event.validation.warnings) lines.push(`- ${warning.code}: ${warning.message}`);
|
|
415
448
|
}
|
|
449
|
+
lines.push(...summarizePendingRepairs(event));
|
|
416
450
|
return lines.join("\n");
|
|
417
451
|
};
|
|
418
452
|
const surveyChangeTool = createTool({
|
|
419
|
-
id: TOOL_ID$
|
|
453
|
+
id: TOOL_ID$5,
|
|
420
454
|
description: "Add one bounded typed-operation delta to the request-scoped survey draft. Successful calls are immediately visible to every later tool in this turn. Rejected calls leave the draft unchanged; retry the failed target. The live editor changes once after successful completion. Every operation uses \"kind\".",
|
|
421
455
|
inputSchema: inputSchema$9,
|
|
422
456
|
outputSchema: surveyChangeSetSchema,
|
|
@@ -431,13 +465,20 @@ const surveyChangeTool = createTool({
|
|
|
431
465
|
const { turnDraft, turnId } = requireSurveyAssistantTurnState(context?.requestContext);
|
|
432
466
|
const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
|
|
433
467
|
const defaultLocale = snapshot?.ui.selectedLocale ?? snapshot?.inspection.locales[0] ?? "en";
|
|
434
|
-
const
|
|
468
|
+
const normalized = normalizeSurveyChangeOperations(inputData.operations, defaultLocale);
|
|
469
|
+
if (normalized.issues.length > 0) return turnDraft.reject(rejectedOperationParseEvent({
|
|
470
|
+
summary: inputData.summary,
|
|
471
|
+
turnId,
|
|
472
|
+
issueText: normalized.issues.slice(0, 8).map((issue) => `${issue.path}: ${issue.message}`).join("; ")
|
|
473
|
+
}), collectMalformedSurveyOperationRecoveryTargets(normalized.operations, snapshot, normalized.issues));
|
|
474
|
+
const operationsParse = z.array(surveyOperationSchema).safeParse(normalized.operations);
|
|
435
475
|
if (!operationsParse.success) return turnDraft.reject(rejectedOperationParseEvent({
|
|
436
476
|
summary: inputData.summary,
|
|
437
477
|
turnId,
|
|
438
478
|
issueText: operationsParse.error.issues.slice(0, 8).map((issue) => `${issue.path.join(".") || "operation"}: ${issue.message}`).join("; ")
|
|
439
|
-
}));
|
|
479
|
+
}), collectMalformedSurveyOperationRecoveryTargets(normalized.operations, snapshot, normalized.issues));
|
|
440
480
|
return turnDraft.stage({
|
|
481
|
+
retryOfChangeIds: inputData.retryOfChangeIds,
|
|
441
482
|
operations: operationsParse.data,
|
|
442
483
|
snapshot,
|
|
443
484
|
summary: inputData.summary
|
|
@@ -447,28 +488,39 @@ const surveyChangeTool = createTool({
|
|
|
447
488
|
});
|
|
448
489
|
//#endregion
|
|
449
490
|
//#region src/server/tools/survey-expression-tool.ts
|
|
450
|
-
const TOOL_ID$
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
reason: z.string().max(500).optional(),
|
|
454
|
-
itemIds: z.array(z.string().min(1).max(240)).max(12).optional(),
|
|
455
|
-
includeRawLocations: z.boolean().optional()
|
|
456
|
-
});
|
|
491
|
+
const TOOL_ID$4 = "survey_expression";
|
|
492
|
+
const MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL = 40;
|
|
493
|
+
const expressionMutationListSchema = z.array(expressionMutationSchema).min(1).max(MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL);
|
|
457
494
|
const prepareActionSchema = z.object({
|
|
458
495
|
action: z.literal("prepare"),
|
|
459
496
|
summary: z.string().min(1).max(500),
|
|
460
|
-
mutations:
|
|
497
|
+
mutations: expressionMutationListSchema
|
|
461
498
|
});
|
|
462
499
|
const inputSchema$8 = z.object({
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
500
|
+
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
501
|
+
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
|
+
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 ids, keys, or fullKeys. Omit to inspect survey-wide references, including controlling questions outside the conditioned item."),
|
|
504
|
+
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
|
+
limit: z.number().int().min(1).max(80).optional().describe("Context action only: rows per catalog page; defaults to 80."),
|
|
506
|
+
includeRawLocations: z.boolean().optional().describe("Context action only: include existing expression locations by raw item."),
|
|
507
|
+
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-item-display-condition","itemId":"<existing-item-id>","expression":{"type":"function","functionName":"eq","arguments":[...]}}. expression is CASE expression JSON, never an expression-language snippet.`)
|
|
469
509
|
}).strict().superRefine((value, ctx) => {
|
|
470
|
-
if (value.action
|
|
471
|
-
|
|
510
|
+
if (value.action === "context") {
|
|
511
|
+
if (value.summary?.trim()) ctx.addIssue({
|
|
512
|
+
code: z.ZodIssueCode.custom,
|
|
513
|
+
path: ["summary"],
|
|
514
|
+
message: "Omit summary for context; use action=prepare to stage mutations."
|
|
515
|
+
});
|
|
516
|
+
if (value.mutations !== void 0) ctx.addIssue({
|
|
517
|
+
code: z.ZodIssueCode.custom,
|
|
518
|
+
path: ["mutations"],
|
|
519
|
+
message: "Omit mutations for context; use action=prepare to stage mutations."
|
|
520
|
+
});
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (!value.summary?.trim()) ctx.addIssue({
|
|
472
524
|
code: z.ZodIssueCode.custom,
|
|
473
525
|
path: ["summary"],
|
|
474
526
|
message: "Required for prepare."
|
|
@@ -476,7 +528,7 @@ const inputSchema$8 = z.object({
|
|
|
476
528
|
if (!value.mutations?.length) ctx.addIssue({
|
|
477
529
|
code: z.ZodIssueCode.custom,
|
|
478
530
|
path: ["mutations"],
|
|
479
|
-
message: "
|
|
531
|
+
message: "A non-empty mutation array is required for prepare."
|
|
480
532
|
});
|
|
481
533
|
});
|
|
482
534
|
const contextOutputSchema = z.object({
|
|
@@ -484,6 +536,11 @@ const contextOutputSchema = z.object({
|
|
|
484
536
|
action: z.literal("context"),
|
|
485
537
|
modelSummary: z.string(),
|
|
486
538
|
listOrdering: z.record(z.string(), z.string()).describe("Meaning of each returned list. Expression references are not positional."),
|
|
539
|
+
nextCursor: z.string().optional(),
|
|
540
|
+
totalResponseSlots: z.number().int().nonnegative().optional(),
|
|
541
|
+
surveyResponseSlotCount: z.number().int().nonnegative().optional(),
|
|
542
|
+
totalExpressionLocations: z.number().int().nonnegative().optional(),
|
|
543
|
+
unresolvedItemIds: z.array(z.string()).optional(),
|
|
487
544
|
responseSlots: z.array(z.object({
|
|
488
545
|
ref: z.string(),
|
|
489
546
|
valueType: z.string().optional()
|
|
@@ -501,22 +558,6 @@ const createIssue = ({ severity = "error", ...input }) => ({
|
|
|
501
558
|
severity,
|
|
502
559
|
...input
|
|
503
560
|
});
|
|
504
|
-
const expressionMutationTarget = (mutation) => {
|
|
505
|
-
switch (mutation.kind) {
|
|
506
|
-
case "set-item-display-condition":
|
|
507
|
-
case "clear-item-display-condition": return `expression:item:${mutation.itemId}:display`;
|
|
508
|
-
case "set-component-display-condition":
|
|
509
|
-
case "clear-component-display-condition": return `expression:item:${mutation.itemId}:component:${mutation.componentId}:display`;
|
|
510
|
-
case "set-component-disabled-condition":
|
|
511
|
-
case "clear-component-disabled-condition": return `expression:item:${mutation.itemId}:component:${mutation.componentId}:disabled`;
|
|
512
|
-
case "set-validation-expression":
|
|
513
|
-
case "clear-validation-expression": return `expression:item:${mutation.itemId}:validation:${mutation.validationKey}`;
|
|
514
|
-
case "set-prefill": return `expression:item:${mutation.itemId}:prefill:${mutation.prefill.id}`;
|
|
515
|
-
case "clear-prefill": return `expression:item:${mutation.itemId}:prefill:${mutation.prefillId}`;
|
|
516
|
-
case "set-template-value":
|
|
517
|
-
case "clear-template-value": return `expression:template:${mutation.templateKey}`;
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
561
|
const buildResponseSlotTypes = (snapshot) => snapshot.responseSlotTypes;
|
|
521
562
|
const rejectedEvent = ({ issues, summary, turnId, warnings = [] }) => ({
|
|
522
563
|
protocolVersion: 1,
|
|
@@ -545,17 +586,26 @@ const summarizeDraftEvent = (event) => {
|
|
|
545
586
|
lines.push("Warnings:");
|
|
546
587
|
for (const warning of event.validation.warnings) lines.push(`- ${warning.code}: ${warning.message}`);
|
|
547
588
|
}
|
|
589
|
+
lines.push(...summarizePendingRepairs(event));
|
|
548
590
|
return lines.join("\n");
|
|
549
591
|
};
|
|
550
592
|
const buildContextOutput = (snapshot, input) => {
|
|
551
593
|
const rawItems = getRawSurveyItems(snapshot);
|
|
552
|
-
const
|
|
553
|
-
|
|
594
|
+
const resolvedItems = input.itemIds?.map((identifier) => ({
|
|
595
|
+
identifier,
|
|
596
|
+
itemId: snapshot.inspection.keyIndex.find((entry) => entry.itemId === identifier || entry.key === identifier || entry.fullKey === identifier)?.itemId
|
|
597
|
+
}));
|
|
598
|
+
const itemFilter = resolvedItems?.length ? new Set(resolvedItems.flatMap((entry) => entry.itemId ? [entry.itemId] : [])) : void 0;
|
|
599
|
+
const unresolvedItemIds = resolvedItems?.filter((entry) => !entry.itemId).map((entry) => entry.identifier) ?? [];
|
|
600
|
+
const surveyResponseSlots = Object.entries(buildResponseSlotTypes(snapshot));
|
|
601
|
+
const allResponseSlots = surveyResponseSlots.filter(([ref]) => !itemFilter || [...itemFilter].some((id) => ref.startsWith(`${id}...`))).map(([ref, valueType]) => ({
|
|
554
602
|
ref,
|
|
555
603
|
...valueType ? { valueType } : {}
|
|
556
604
|
}));
|
|
557
|
-
const
|
|
558
|
-
const
|
|
605
|
+
const offset = Number(input.cursor ?? 0);
|
|
606
|
+
const limit = input.limit ?? 80;
|
|
607
|
+
const responseSlots = allResponseSlots.slice(offset, offset + limit);
|
|
608
|
+
const allExpressionLocations = input.action === "context" && input.includeRawLocations === true ? rawItems.map((item, rawIndex) => {
|
|
559
609
|
if (itemFilter && !itemFilter.has(item.id) && !itemFilter.has(item.key)) return;
|
|
560
610
|
const locations = [
|
|
561
611
|
item.displayConditions?.root ? "displayConditions.root" : void 0,
|
|
@@ -572,6 +622,9 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
572
622
|
locations
|
|
573
623
|
};
|
|
574
624
|
}).filter((entry) => Boolean(entry)) : void 0;
|
|
625
|
+
const expressionLocations = allExpressionLocations?.slice(offset, offset + limit);
|
|
626
|
+
const totalAvailable = Math.max(allResponseSlots.length, allExpressionLocations?.length ?? 0);
|
|
627
|
+
const nextCursor = offset + limit < totalAvailable ? String(offset + limit) : void 0;
|
|
575
628
|
return {
|
|
576
629
|
ok: true,
|
|
577
630
|
action: "context",
|
|
@@ -580,13 +633,23 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
580
633
|
"Use prepare mutations for display conditions, disabled conditions, validations, prefills, and template values.",
|
|
581
634
|
"Display, disabled, validation, and prefill.when expressions must return boolean.",
|
|
582
635
|
"Use response refs exactly as listed and check their value types before choosing functions.",
|
|
636
|
+
"An isDefined ref means only that the respondent answered; it never means a particular answer such as Yes, selected, eligible, or has children.",
|
|
637
|
+
"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
|
+
"Avoid accidental item display-condition response cycles, including self-reference or A-to-B-to-A chains. Shared analysis reports them as advisory because prefills can make them intentional; prefer an acyclic controlling source item unless the dependency is deliberate.",
|
|
583
639
|
"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[].",
|
|
584
640
|
"Response-slot order: set-like deterministic snapshot construction order; it has no survey or respondent-visible meaning.",
|
|
585
|
-
`Response
|
|
586
|
-
responseSlots.length
|
|
641
|
+
itemFilter ? `Response-slot scope: filtered to itemIds=${JSON.stringify(input.itemIds)}; ${allResponseSlots.length} matching of ${surveyResponseSlots.length} 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 (${surveyResponseSlots.length} slots).`,
|
|
642
|
+
`Response slots (${responseSlots.length}/${allResponseSlots.length}, offset ${offset}): ${responseSlots.map((slot) => `${slot.ref}${slot.valueType ? `:${slot.valueType}` : ""}`).join(", ")}`,
|
|
643
|
+
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
|
+
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,
|
|
587
645
|
"Functions: and, or, not, eq, gt, gte, lt, lte, in_range, list_contains, sum, min, max, str_eq, date_eq.",
|
|
588
646
|
expressionLocations ? `Existing expression locations: ${expressionLocations.filter((entry) => entry.locations.length > 0).map((entry) => `${entry.key}(${entry.itemId}) -> ${entry.locations.join("|")}`).join("; ") || "none"}` : void 0
|
|
589
647
|
].filter((line) => Boolean(line)).join("\n"),
|
|
648
|
+
totalResponseSlots: allResponseSlots.length,
|
|
649
|
+
surveyResponseSlotCount: surveyResponseSlots.length,
|
|
650
|
+
...allExpressionLocations ? { totalExpressionLocations: allExpressionLocations.length } : {},
|
|
651
|
+
...nextCursor ? { nextCursor } : {},
|
|
652
|
+
...unresolvedItemIds.length ? { unresolvedItemIds } : {},
|
|
590
653
|
listOrdering: {
|
|
591
654
|
responseSlots: "Set-like deterministic snapshot construction order; position has no survey or respondent-visible meaning.",
|
|
592
655
|
expressionLocations: "Stored surveyItems array order; locations within an item are grouped by expression surface, not ranked or respondent-visible order.",
|
|
@@ -597,7 +660,7 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
597
660
|
};
|
|
598
661
|
};
|
|
599
662
|
const surveyExpressionTool = createTool({
|
|
600
|
-
id: TOOL_ID$
|
|
663
|
+
id: TOOL_ID$4,
|
|
601
664
|
description: "Inspect response refs or add general expression edits to the request-scoped survey draft. Successful mutation calls are immediately visible to later tools; rejected calls leave the draft unchanged. Covers display and disabled conditions, validations, prefills, and template values.",
|
|
602
665
|
inputSchema: inputSchema$8,
|
|
603
666
|
outputSchema: outputSchema$7,
|
|
@@ -630,13 +693,22 @@ const surveyExpressionTool = createTool({
|
|
|
630
693
|
})]
|
|
631
694
|
}));
|
|
632
695
|
}
|
|
633
|
-
if (input.action === "context")
|
|
634
|
-
const contextParse = contextActionSchema.safeParse(inputData);
|
|
635
|
-
return buildContextOutput(snapshot, contextParse.success ? contextParse.data : input);
|
|
636
|
-
}
|
|
696
|
+
if (input.action === "context") return buildContextOutput(snapshot, input);
|
|
637
697
|
const { turnDraft, turnId } = requireSurveyAssistantTurnState(requestContext);
|
|
638
|
-
const reject = (event, addressedTargets = []) => turnDraft.reject(event, addressedTargets);
|
|
639
|
-
|
|
698
|
+
const reject = (event, addressedTargets = [], refinements = []) => turnDraft.reject(event, addressedTargets, refinements);
|
|
699
|
+
if (!Array.isArray(input.mutations)) return reject(rejectedEvent({
|
|
700
|
+
summary: input.summary ?? "Expression change",
|
|
701
|
+
turnId,
|
|
702
|
+
issues: [createIssue({
|
|
703
|
+
code: "invalid-expression-mutation-shape",
|
|
704
|
+
message: "mutations is required for prepare and must be a canonical mutation array."
|
|
705
|
+
})]
|
|
706
|
+
}));
|
|
707
|
+
const prepareParse = prepareActionSchema.safeParse({
|
|
708
|
+
action: input.action,
|
|
709
|
+
summary: input.summary,
|
|
710
|
+
mutations: input.mutations
|
|
711
|
+
});
|
|
640
712
|
if (!prepareParse.success) return reject(rejectedEvent({
|
|
641
713
|
summary: input.summary ?? "Expression change",
|
|
642
714
|
turnId,
|
|
@@ -644,22 +716,10 @@ const surveyExpressionTool = createTool({
|
|
|
644
716
|
code: "invalid-expression-mutation-shape",
|
|
645
717
|
message: prepareParse.error.issues.slice(0, 8).map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ")
|
|
646
718
|
})]
|
|
647
|
-
}));
|
|
719
|
+
}), collectExpressionMutationRecoveryTargets(input.mutations, snapshot));
|
|
648
720
|
const prepareInput = prepareParse.data;
|
|
649
|
-
const addressedTargets =
|
|
650
|
-
const issues =
|
|
651
|
-
const patches = [];
|
|
652
|
-
const responseSlotTypes = buildResponseSlotTypes(snapshot);
|
|
653
|
-
prepareInput.mutations.forEach((mutation, operationIndex) => {
|
|
654
|
-
buildExpressionMutationPatches({
|
|
655
|
-
mutation,
|
|
656
|
-
operationIndex,
|
|
657
|
-
issues,
|
|
658
|
-
patches,
|
|
659
|
-
responseSlotTypes,
|
|
660
|
-
snapshot
|
|
661
|
-
});
|
|
662
|
-
});
|
|
721
|
+
const addressedTargets = collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot);
|
|
722
|
+
const { issues, patches } = buildExpressionMutationBatch(prepareInput.mutations, snapshot);
|
|
663
723
|
const errors = issues.filter((issue) => issue.severity === "error");
|
|
664
724
|
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
665
725
|
if (errors.length > 0) return reject(rejectedEvent({
|
|
@@ -667,21 +727,26 @@ const surveyExpressionTool = createTool({
|
|
|
667
727
|
turnId,
|
|
668
728
|
issues: errors,
|
|
669
729
|
warnings
|
|
670
|
-
}),
|
|
671
|
-
if (patches.length === 0)
|
|
672
|
-
|
|
673
|
-
turnId,
|
|
674
|
-
issues: [createIssue({
|
|
730
|
+
}), collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot, errors), collectExpressionMutationRecoveryRefinements(prepareInput.mutations, snapshot, errors));
|
|
731
|
+
if (patches.length === 0) {
|
|
732
|
+
const noPatchIssues = prepareInput.mutations.map((_, operationIndex) => createIssue({
|
|
675
733
|
code: "no-expression-patches",
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
734
|
+
operationIndex,
|
|
735
|
+
message: "No expression patches were produced. Inspect whether the requested locations already match the target state; acknowledge already-satisfied work through a linked survey_change with operations: []."
|
|
736
|
+
}));
|
|
737
|
+
return reject(rejectedEvent({
|
|
738
|
+
summary: prepareInput.summary,
|
|
739
|
+
turnId,
|
|
740
|
+
issues: noPatchIssues,
|
|
741
|
+
warnings
|
|
742
|
+
}), collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot, noPatchIssues), collectExpressionMutationRecoveryRefinements(prepareInput.mutations, snapshot, noPatchIssues));
|
|
743
|
+
}
|
|
680
744
|
const operation = {
|
|
681
745
|
kind: "survey-json-patch",
|
|
682
746
|
patches
|
|
683
747
|
};
|
|
684
748
|
const event = turnDraft.stage({
|
|
749
|
+
retryOfChangeIds: input.retryOfChangeIds,
|
|
685
750
|
addressedTargets,
|
|
686
751
|
allowExpressionJsonPatches: true,
|
|
687
752
|
handledMutationRoutes: ["expression"],
|
|
@@ -700,8 +765,14 @@ const surveyExpressionTool = createTool({
|
|
|
700
765
|
toModelOutput: (value) => toTextModelOutput(value, (output) => "action" in output && output.action === "context" ? output.modelSummary : summarizeDraftEvent(output))
|
|
701
766
|
});
|
|
702
767
|
//#endregion
|
|
703
|
-
//#region src/server/tools/
|
|
704
|
-
const
|
|
768
|
+
//#region src/server/tools/parse-inspection-output.ts
|
|
769
|
+
const parseInspectionOutput = (schema, value, backend) => {
|
|
770
|
+
const parsed = schema.safeParse(value);
|
|
771
|
+
if (!parsed.success) throw new Error(`${backend} produced invalid inspection output.`, { cause: parsed.error });
|
|
772
|
+
return parsed.data;
|
|
773
|
+
};
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/server/tools/inspect-current-context.ts
|
|
705
776
|
const contextScopeSchema = z.enum([
|
|
706
777
|
"summary",
|
|
707
778
|
"selected-item",
|
|
@@ -723,7 +794,7 @@ const translationSnippetSchema = z.object({
|
|
|
723
794
|
const inputSchema$7 = z.object({
|
|
724
795
|
scopes: z.array(contextScopeSchema).min(1).default(["summary"]).describe("Which current editor context sections to inspect."),
|
|
725
796
|
itemId: z.string().optional().describe("Optional item id to inspect. Defaults to the selected/focused item."),
|
|
726
|
-
query: z.string().max(
|
|
797
|
+
query: z.string().max(180).optional().describe("Optional text used to find matching item ids, keys, labels, or response slots."),
|
|
727
798
|
cursor: z.string().optional().describe("Optional numeric cursor returned by a previous outline or response slot request."),
|
|
728
799
|
outlineMode: outlineModeSchema$1.default("full").optional().describe("For outline scope: \"full\" pages the complete survey key index, \"focused\" returns the selected/focused neighborhood, and \"children\" returns direct children of itemId."),
|
|
729
800
|
limit: z.number().int().min(1).max(50).default(12).describe("Maximum number of rows to return for list-like scopes.")
|
|
@@ -962,128 +1033,67 @@ const buildResponseSlots = (snapshot, input) => {
|
|
|
962
1033
|
slots: page.items
|
|
963
1034
|
};
|
|
964
1035
|
};
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
if (
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1036
|
+
const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
1037
|
+
const input = inputSchema$7.parse({
|
|
1038
|
+
...inputData,
|
|
1039
|
+
outlineMode: inputData.outlineMode ?? "full"
|
|
1040
|
+
});
|
|
1041
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1042
|
+
if (!snapshot) return {
|
|
1043
|
+
ok: false,
|
|
1044
|
+
reason: "No live editor snapshot was provided with this assistant turn.",
|
|
1045
|
+
scopes: input.scopes
|
|
1046
|
+
};
|
|
1047
|
+
const scopes = unique(input.scopes);
|
|
1048
|
+
const listOrdering = {};
|
|
1049
|
+
if (scopes.includes("summary")) Object.assign(listOrdering, {
|
|
1050
|
+
"summary.recentItemIds": "Most-recent-first UI history; not survey structure.",
|
|
1051
|
+
"summary.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1052
|
+
"summary.availableItemTypes": "Capability registry order; treat as a set of available types.",
|
|
1053
|
+
"summary.deterministicCreateItemTypes": "Capability registry order; treat as a set of available types.",
|
|
1054
|
+
"summary.itemTypeDetails": "Capability registry order; treat as a set of available type definitions."
|
|
1055
|
+
});
|
|
1056
|
+
if (scopes.includes("selected-item")) Object.assign(listOrdering, {
|
|
1057
|
+
"selectedItem.pathItems": "Root-to-selected survey-tree path.",
|
|
1058
|
+
"selectedItem.siblingItems": "Source survey item storage order; not necessarily respondent-visible order.",
|
|
1059
|
+
"selectedItem.childItems": "Stored group-child order when the focused item is a group.",
|
|
1060
|
+
"selectedItem.translationSnippets": "Grouped by locale and then source translation-key order; not positional survey content.",
|
|
1061
|
+
"selectedItem.descendantItems": "Depth-first survey-tree traversal from the selected item."
|
|
1062
|
+
});
|
|
1063
|
+
if (scopes.includes("ui-context")) Object.assign(listOrdering, {
|
|
1064
|
+
"uiContext.openPanels": "UI-provided set-like list; position has no meaning.",
|
|
1065
|
+
"uiContext.highlights": "UI-provided set-like list; position has no meaning."
|
|
1066
|
+
});
|
|
1067
|
+
if (scopes.includes("history")) listOrdering["history.recent"] = "Chronological editor history, oldest to newest within this returned window.";
|
|
1068
|
+
const output = {
|
|
1069
|
+
ok: true,
|
|
1070
|
+
revision: snapshot.version.revision,
|
|
1071
|
+
contentHash: snapshot.version.contentHash,
|
|
1072
|
+
scopes,
|
|
1073
|
+
...Object.keys(listOrdering).length > 0 ? { listOrdering } : {}
|
|
1074
|
+
};
|
|
1075
|
+
if (scopes.includes("summary")) output.summary = buildSummary(snapshot);
|
|
1076
|
+
if (scopes.includes("selected-item")) output.selectedItem = buildSelectedItem(snapshot, input);
|
|
1077
|
+
if (scopes.includes("outline")) output.outline = buildOutline(snapshot, input);
|
|
1078
|
+
if (scopes.includes("response-slots")) output.responseSlots = buildResponseSlots(snapshot, input);
|
|
1079
|
+
if (scopes.includes("ui-context")) output.uiContext = {
|
|
1080
|
+
activeSurface: snapshot.ui.activeSurface,
|
|
1081
|
+
openPanels: snapshot.ui.openPanels ?? [],
|
|
1082
|
+
highlights: snapshot.ui.highlights ?? []
|
|
1083
|
+
};
|
|
1084
|
+
if (scopes.includes("history") && snapshot.ui.history) output.history = snapshot.ui.history;
|
|
1085
|
+
return parseInspectionOutput(outputSchema$6, output, "Current-context inspection");
|
|
1005
1086
|
};
|
|
1006
|
-
const surveyGetCurrentContextTool = createTool({
|
|
1007
|
-
id: TOOL_ID$7,
|
|
1008
|
-
description: "Read the current Survey Editor context from the latest request snapshot. Use for questions about this screen, selected item, survey outline, item keys, labels, locales, or response slots. The outline scope can page the complete survey key index, so use cursor/limit instead of guessing when a focused outline is truncated.",
|
|
1009
|
-
inputSchema: inputSchema$7,
|
|
1010
|
-
outputSchema: outputSchema$6,
|
|
1011
|
-
mcp: { annotations: {
|
|
1012
|
-
title: "Get current survey context",
|
|
1013
|
-
readOnlyHint: true,
|
|
1014
|
-
destructiveHint: false,
|
|
1015
|
-
idempotentHint: true,
|
|
1016
|
-
openWorldHint: false
|
|
1017
|
-
} },
|
|
1018
|
-
execute: async (inputData, context) => {
|
|
1019
|
-
const input = {
|
|
1020
|
-
...inputData,
|
|
1021
|
-
scopes: inputData.scopes ?? ["summary"],
|
|
1022
|
-
limit: inputData.limit ?? 12,
|
|
1023
|
-
outlineMode: inputData.outlineMode ?? "full"
|
|
1024
|
-
};
|
|
1025
|
-
const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
|
|
1026
|
-
if (!snapshot) return {
|
|
1027
|
-
ok: false,
|
|
1028
|
-
reason: "No live editor snapshot was provided with this assistant turn.",
|
|
1029
|
-
scopes: input.scopes
|
|
1030
|
-
};
|
|
1031
|
-
const scopes = unique(input.scopes);
|
|
1032
|
-
const listOrdering = {};
|
|
1033
|
-
if (scopes.includes("summary")) Object.assign(listOrdering, {
|
|
1034
|
-
"summary.recentItemIds": "Most-recent-first UI history; not survey structure.",
|
|
1035
|
-
"summary.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1036
|
-
"summary.availableItemTypes": "Capability registry order; treat as a set of available types.",
|
|
1037
|
-
"summary.deterministicCreateItemTypes": "Capability registry order; treat as a set of available types.",
|
|
1038
|
-
"summary.itemTypeDetails": "Capability registry order; treat as a set of available type definitions."
|
|
1039
|
-
});
|
|
1040
|
-
if (scopes.includes("selected-item")) Object.assign(listOrdering, {
|
|
1041
|
-
"selectedItem.pathItems": "Root-to-selected survey-tree path.",
|
|
1042
|
-
"selectedItem.siblingItems": "Source survey item storage order; not necessarily respondent-visible order.",
|
|
1043
|
-
"selectedItem.childItems": "Stored group-child order when the focused item is a group.",
|
|
1044
|
-
"selectedItem.translationSnippets": "Grouped by locale and then source translation-key order; not positional survey content.",
|
|
1045
|
-
"selectedItem.descendantItems": "Depth-first survey-tree traversal from the selected item."
|
|
1046
|
-
});
|
|
1047
|
-
if (scopes.includes("ui-context")) Object.assign(listOrdering, {
|
|
1048
|
-
"uiContext.openPanels": "UI-provided set-like list; position has no meaning.",
|
|
1049
|
-
"uiContext.highlights": "UI-provided set-like list; position has no meaning."
|
|
1050
|
-
});
|
|
1051
|
-
if (scopes.includes("history")) listOrdering["history.recent"] = "Chronological editor history, oldest to newest within this returned window.";
|
|
1052
|
-
const output = {
|
|
1053
|
-
ok: true,
|
|
1054
|
-
revision: snapshot.version.revision,
|
|
1055
|
-
contentHash: snapshot.version.contentHash,
|
|
1056
|
-
scopes,
|
|
1057
|
-
...Object.keys(listOrdering).length > 0 ? { listOrdering } : {}
|
|
1058
|
-
};
|
|
1059
|
-
if (scopes.includes("summary")) output.summary = buildSummary(snapshot);
|
|
1060
|
-
if (scopes.includes("selected-item")) output.selectedItem = buildSelectedItem(snapshot, input);
|
|
1061
|
-
if (scopes.includes("outline")) output.outline = buildOutline(snapshot, input);
|
|
1062
|
-
if (scopes.includes("response-slots")) output.responseSlots = buildResponseSlots(snapshot, input);
|
|
1063
|
-
if (scopes.includes("ui-context")) output.uiContext = {
|
|
1064
|
-
activeSurface: snapshot.ui.activeSurface,
|
|
1065
|
-
openPanels: snapshot.ui.openPanels ?? [],
|
|
1066
|
-
highlights: snapshot.ui.highlights ?? []
|
|
1067
|
-
};
|
|
1068
|
-
if (scopes.includes("history") && snapshot.ui.history) output.history = snapshot.ui.history;
|
|
1069
|
-
return output;
|
|
1070
|
-
},
|
|
1071
|
-
toModelOutput: (value) => toTextModelOutput(value, summarizeForModel$3)
|
|
1072
|
-
});
|
|
1073
1087
|
//#endregion
|
|
1074
|
-
//#region src/server/tools/
|
|
1075
|
-
const TOOL_ID$6 = "survey_get_item_details";
|
|
1076
|
-
const DEFAULT_TEXT_LIMIT$1 = 400;
|
|
1077
|
-
const MAX_TEXT_LIMIT = 2e3;
|
|
1078
|
-
const DEFAULT_CHILDREN_LIMIT = 20;
|
|
1088
|
+
//#region src/server/tools/inspect-item-details.ts
|
|
1079
1089
|
const inputSchema$6 = z.object({
|
|
1080
1090
|
itemId: z.string().describe("Identifier of the item to inspect: its itemId, key (e.g. \"info_1\"), or fullKey (e.g. \"survey.info_1\"). Discover candidates first via survey_inspect search or current context."),
|
|
1081
1091
|
locales: z.array(z.string()).optional().describe("Optional locale codes to restrict returned translations to. Defaults to all locales."),
|
|
1082
|
-
textLimit: z.number().int().min(20).max(
|
|
1092
|
+
textLimit: z.number().int().min(20).max(2e3).default(400).describe("Maximum characters returned per translation snippet."),
|
|
1083
1093
|
includeChildren: z.boolean().optional().describe("Deprecated alias for includeSurveyChildren. This means survey-tree children only, not choice options or form fields."),
|
|
1084
1094
|
includeSurveyChildren: z.boolean().default(false).describe("Whether to include a shallow list of this item's direct survey-tree children, such as group members."),
|
|
1085
1095
|
includeItemConfig: z.boolean().default(true).describe("Whether to include item-internal config details such as choice options and form field groups."),
|
|
1086
|
-
childrenLimit: z.number().int().min(1).max(50).default(
|
|
1096
|
+
childrenLimit: z.number().int().min(1).max(50).default(20).describe("Maximum number of direct survey-tree children to return.")
|
|
1087
1097
|
});
|
|
1088
1098
|
const outputSchema$5 = z.object({
|
|
1089
1099
|
ok: z.boolean(),
|
|
@@ -1099,129 +1109,68 @@ const outputSchema$5 = z.object({
|
|
|
1099
1109
|
details: z.unknown().optional()
|
|
1100
1110
|
}).optional()
|
|
1101
1111
|
});
|
|
1102
|
-
const
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1112
|
+
const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
1113
|
+
const input = inputSchema$6.parse({
|
|
1114
|
+
...inputData,
|
|
1115
|
+
includeSurveyChildren: inputData.includeSurveyChildren ?? inputData.includeChildren
|
|
1116
|
+
});
|
|
1117
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1118
|
+
if (!snapshot) return {
|
|
1119
|
+
ok: false,
|
|
1120
|
+
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1121
|
+
};
|
|
1122
|
+
const outlineById = mapOutlineById(snapshot);
|
|
1123
|
+
const indexById = mapKeyIndexById(snapshot);
|
|
1124
|
+
const rawItemsById = mapRawItemsById(snapshot);
|
|
1125
|
+
const item = resolveItemSummary(input.itemId, indexById, outlineById);
|
|
1126
|
+
if (!item) return {
|
|
1127
|
+
ok: false,
|
|
1128
|
+
reason: `No item matching "${input.itemId}" (by itemId, key, or fullKey) was found in the current context snapshot.`
|
|
1129
|
+
};
|
|
1130
|
+
const rawItem = rawItemsById.get(item.itemId);
|
|
1131
|
+
const rawConfig = rawItem?.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1132
|
+
const output = {
|
|
1133
|
+
ok: true,
|
|
1134
|
+
item,
|
|
1135
|
+
translations: buildItemTranslationEntries(snapshot, item.itemId, input),
|
|
1136
|
+
listOrdering: {
|
|
1137
|
+
translations: "Grouped by locale and then source translation-key order; this list is not positional survey content.",
|
|
1138
|
+
surveyChildren: rawConfig?.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."
|
|
1110
1139
|
}
|
|
1140
|
+
};
|
|
1141
|
+
if (input.includeSurveyChildren) {
|
|
1142
|
+
const allChildren = findDirectChildrenByFullKey(getKeyIndexEntries(snapshot), outlineById, item.fullKey);
|
|
1143
|
+
const limit = clampLimit(input.childrenLimit);
|
|
1144
|
+
output.surveyChildren = allChildren.slice(0, limit);
|
|
1145
|
+
output.surveyChildrenTruncated = allChildren.length > limit;
|
|
1111
1146
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1147
|
+
if (input.includeItemConfig) {
|
|
1148
|
+
output.configDetails = rawItem ? buildItemConfigDetails(rawItem) : void 0;
|
|
1149
|
+
output.capabilityDetails = rawItem ? inspectAssistantItem({
|
|
1150
|
+
capabilities: snapshot.trustedCapabilities,
|
|
1151
|
+
item: rawItem
|
|
1152
|
+
}) : void 0;
|
|
1153
|
+
if (rawItem?.itemType === "choiceItem" && output.configDetails?.choiceOptions) {
|
|
1154
|
+
const rawChoiceConfig = rawItem.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1155
|
+
const rawOptions = Array.isArray(rawChoiceConfig?.options) ? rawChoiceConfig.options : [];
|
|
1156
|
+
for (const option of output.configDetails.choiceOptions) {
|
|
1157
|
+
const rawOption = rawOptions.find((candidate) => candidate && typeof candidate === "object" && "id" in candidate && candidate.id === option.id);
|
|
1158
|
+
const embeddedFormDetails = buildEmbeddedFormDetails(option, rawOption && typeof rawOption === "object" && "embeddedForm" in rawOption ? rawOption.embeddedForm : void 0);
|
|
1159
|
+
if (embeddedFormDetails?.formFieldGroups?.length) output.configDetails.formFieldGroups = [...output.configDetails.formFieldGroups ?? [], ...embeddedFormDetails.formFieldGroups];
|
|
1160
|
+
}
|
|
1121
1161
|
}
|
|
1122
1162
|
}
|
|
1123
|
-
return
|
|
1124
|
-
};
|
|
1125
|
-
const summarizeForModel$2 = (output) => {
|
|
1126
|
-
if (!output.ok) return output.reason ?? "Item details are unavailable.";
|
|
1127
|
-
const lines = [];
|
|
1128
|
-
if (output.item) lines.push(`Item: ${output.item.fullKey} (${output.item.itemType})${output.item.itemLabel ? ` - ${output.item.itemLabel}` : ""}.`);
|
|
1129
|
-
if (output.translations?.length) lines.push(`Translations: ${output.translations.map((entry) => `${entry.locale}.${entry.contentKey}: ${entry.text}`).join("; ")}`);
|
|
1130
|
-
else lines.push("Translations: none found for this item.");
|
|
1131
|
-
if (output.surveyChildren) {
|
|
1132
|
-
lines.push(`Survey-tree children (${output.surveyChildren.length}):`);
|
|
1133
|
-
for (const child of output.surveyChildren) lines.push(`- ${child.fullKey} (${child.itemType})${child.itemLabel ? ` - ${child.itemLabel}` : ""}`);
|
|
1134
|
-
if (output.surveyChildrenTruncated) lines.push("More children exist than were returned; narrow down or raise childrenLimit.");
|
|
1135
|
-
}
|
|
1136
|
-
if (output.configDetails) {
|
|
1137
|
-
if (output.configDetails.listOrdering) for (const [list, meaning] of Object.entries(output.configDetails.listOrdering)) lines.push(`List order (${list}): ${meaning}`);
|
|
1138
|
-
lines.push(...summarizeConfigDetails(output.configDetails));
|
|
1139
|
-
}
|
|
1140
|
-
if (output.capabilityDetails) {
|
|
1141
|
-
lines.push(`Item capability: ${output.capabilityDetails.summary}`);
|
|
1142
|
-
if (output.capabilityDetails.details !== void 0) lines.push(`Capability details: ${JSON.stringify(output.capabilityDetails.details)}`);
|
|
1143
|
-
}
|
|
1144
|
-
return lines.join("\n");
|
|
1163
|
+
return parseInspectionOutput(outputSchema$5, output, "Item-details inspection");
|
|
1145
1164
|
};
|
|
1146
|
-
const surveyGetItemDetailsTool = createTool({
|
|
1147
|
-
id: TOOL_ID$6,
|
|
1148
|
-
description: "Fetch full details for a SPECIFIC survey item, whether or not it is currently selected. Returns item translations plus item-internal config details such as choice options and form fields. Use survey_search_items first when you only know visible text.",
|
|
1149
|
-
inputSchema: inputSchema$6,
|
|
1150
|
-
outputSchema: outputSchema$5,
|
|
1151
|
-
mcp: { annotations: {
|
|
1152
|
-
title: "Get survey item details",
|
|
1153
|
-
readOnlyHint: true,
|
|
1154
|
-
destructiveHint: false,
|
|
1155
|
-
idempotentHint: true,
|
|
1156
|
-
openWorldHint: false
|
|
1157
|
-
} },
|
|
1158
|
-
execute: async (inputData, context) => {
|
|
1159
|
-
const input = {
|
|
1160
|
-
...inputData,
|
|
1161
|
-
textLimit: inputData.textLimit ?? DEFAULT_TEXT_LIMIT$1,
|
|
1162
|
-
includeSurveyChildren: inputData.includeSurveyChildren ?? inputData.includeChildren ?? false,
|
|
1163
|
-
includeItemConfig: inputData.includeItemConfig ?? true,
|
|
1164
|
-
childrenLimit: inputData.childrenLimit ?? DEFAULT_CHILDREN_LIMIT
|
|
1165
|
-
};
|
|
1166
|
-
const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
|
|
1167
|
-
if (!snapshot) return {
|
|
1168
|
-
ok: false,
|
|
1169
|
-
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1170
|
-
};
|
|
1171
|
-
const outlineById = mapOutlineById(snapshot);
|
|
1172
|
-
const indexById = mapKeyIndexById(snapshot);
|
|
1173
|
-
const rawItemsById = mapRawItemsById(snapshot);
|
|
1174
|
-
const item = resolveItemSummary(input.itemId, indexById, outlineById);
|
|
1175
|
-
if (!item) return {
|
|
1176
|
-
ok: false,
|
|
1177
|
-
reason: `No item matching "${input.itemId}" (by itemId, key, or fullKey) was found in the current context snapshot.`
|
|
1178
|
-
};
|
|
1179
|
-
const rawItem = rawItemsById.get(item.itemId);
|
|
1180
|
-
const rawConfig = rawItem?.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1181
|
-
const output = {
|
|
1182
|
-
ok: true,
|
|
1183
|
-
item,
|
|
1184
|
-
translations: buildItemTranslationEntries(snapshot, item.itemId, input),
|
|
1185
|
-
listOrdering: {
|
|
1186
|
-
translations: "Grouped by locale and then source translation-key order; this list is not positional survey content.",
|
|
1187
|
-
surveyChildren: rawConfig?.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."
|
|
1188
|
-
}
|
|
1189
|
-
};
|
|
1190
|
-
if (input.includeSurveyChildren) {
|
|
1191
|
-
const allChildren = findDirectChildrenByFullKey(getKeyIndexEntries(snapshot), outlineById, item.fullKey);
|
|
1192
|
-
const limit = clampLimit(input.childrenLimit);
|
|
1193
|
-
output.surveyChildren = allChildren.slice(0, limit);
|
|
1194
|
-
output.surveyChildrenTruncated = allChildren.length > limit;
|
|
1195
|
-
}
|
|
1196
|
-
if (input.includeItemConfig) {
|
|
1197
|
-
output.configDetails = rawItem ? buildItemConfigDetails(rawItem) : void 0;
|
|
1198
|
-
output.capabilityDetails = rawItem ? inspectAssistantItem({
|
|
1199
|
-
capabilities: snapshot.trustedCapabilities,
|
|
1200
|
-
item: rawItem
|
|
1201
|
-
}) : void 0;
|
|
1202
|
-
if (rawItem?.itemType === "choiceItem" && output.configDetails?.choiceOptions) {
|
|
1203
|
-
const rawChoiceConfig = rawItem.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1204
|
-
const rawOptions = Array.isArray(rawChoiceConfig?.options) ? rawChoiceConfig.options : [];
|
|
1205
|
-
for (const option of output.configDetails.choiceOptions) {
|
|
1206
|
-
const rawOption = rawOptions.find((candidate) => candidate && typeof candidate === "object" && "id" in candidate && candidate.id === option.id);
|
|
1207
|
-
const embeddedFormDetails = buildEmbeddedFormDetails(option, rawOption && typeof rawOption === "object" && "embeddedForm" in rawOption ? rawOption.embeddedForm : void 0);
|
|
1208
|
-
if (embeddedFormDetails?.formFieldGroups?.length) output.configDetails.formFieldGroups = [...output.configDetails.formFieldGroups ?? [], ...embeddedFormDetails.formFieldGroups];
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
return output;
|
|
1213
|
-
},
|
|
1214
|
-
toModelOutput: (value) => toTextModelOutput(value, summarizeForModel$2)
|
|
1215
|
-
});
|
|
1216
1165
|
//#endregion
|
|
1217
|
-
//#region src/server/tools/
|
|
1218
|
-
const TOOL_ID$5 = "survey_get_raw_survey";
|
|
1166
|
+
//#region src/server/tools/inspect-raw-survey.ts
|
|
1219
1167
|
const DEFAULT_VALUE_CHARACTER_LIMIT = 12e3;
|
|
1220
1168
|
const MAX_VALUE_CHARACTER_LIMIT = 6e4;
|
|
1221
1169
|
const jsonPointerPathSchema = z.string().max(500).describe("JSON Pointer path into the survey document. Examples: \"/surveyItems/0/config/items\", \"/metadata\", \"/translations/en/itemTranslations/<itemId>/title\".");
|
|
1222
1170
|
const inputSchema$5 = z.object({
|
|
1223
1171
|
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."),
|
|
1224
1172
|
itemIdentifiers: z.array(z.string().min(1).max(240)).min(1).max(20).optional().describe("Optional item ids, keys, or fullKeys to inspect. Returns exact /surveyItems/<index> paths plus item translation paths for each item."),
|
|
1173
|
+
textOffset: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
1225
1174
|
valueCharacterLimit: z.number().int().min(1e3).max(MAX_VALUE_CHARACTER_LIMIT).default(DEFAULT_VALUE_CHARACTER_LIMIT).describe("Maximum serialized characters returned per requested value.")
|
|
1226
1175
|
});
|
|
1227
1176
|
const rawSurveyItemPathSchema = z.object({
|
|
@@ -1250,6 +1199,11 @@ const outputSchema$4 = z.object({
|
|
|
1250
1199
|
found: z.boolean(),
|
|
1251
1200
|
value: z.unknown().optional(),
|
|
1252
1201
|
preview: z.string().optional(),
|
|
1202
|
+
text: z.string().optional(),
|
|
1203
|
+
textOffset: z.number().int().nonnegative().optional(),
|
|
1204
|
+
textLength: z.number().int().nonnegative().optional(),
|
|
1205
|
+
nextTextOffset: z.number().int().nonnegative().optional(),
|
|
1206
|
+
recovery: z.string().optional(),
|
|
1253
1207
|
truncated: z.boolean().optional(),
|
|
1254
1208
|
error: z.string().optional()
|
|
1255
1209
|
})).optional(),
|
|
@@ -1313,10 +1267,27 @@ const buildValues = (snapshot, input) => {
|
|
|
1313
1267
|
const rawSurvey = getRawSurvey(snapshot);
|
|
1314
1268
|
return input.paths.map((path) => {
|
|
1315
1269
|
try {
|
|
1270
|
+
const value = getValueAtJsonPointer(rawSurvey, path);
|
|
1271
|
+
if (typeof value === "string" && (input.textOffset !== void 0 || JSON.stringify(value).length > input.valueCharacterLimit)) {
|
|
1272
|
+
const characters = [...value];
|
|
1273
|
+
const textOffset = input.textOffset ?? 0;
|
|
1274
|
+
const text = characters.slice(textOffset, textOffset + input.valueCharacterLimit).join("");
|
|
1275
|
+
const nextTextOffset = textOffset + [...text].length;
|
|
1276
|
+
return {
|
|
1277
|
+
path,
|
|
1278
|
+
found: true,
|
|
1279
|
+
text,
|
|
1280
|
+
textOffset,
|
|
1281
|
+
textLength: characters.length,
|
|
1282
|
+
truncated: textOffset > 0 || nextTextOffset < characters.length,
|
|
1283
|
+
...nextTextOffset < characters.length ? { nextTextOffset } : {},
|
|
1284
|
+
recovery: "This is a string segment, not the complete value. Concatenate segments in offset order. Continue with survey_inspect action=raw-json, this same path, and textOffset=nextTextOffset until nextTextOffset is absent. Restart after a draft mutation."
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1316
1287
|
return {
|
|
1317
1288
|
path,
|
|
1318
1289
|
found: true,
|
|
1319
|
-
...toOutputValue(
|
|
1290
|
+
...toOutputValue(value, input.valueCharacterLimit)
|
|
1320
1291
|
};
|
|
1321
1292
|
} catch (error) {
|
|
1322
1293
|
return {
|
|
@@ -1379,88 +1350,38 @@ const buildItemValues = (snapshot, input) => {
|
|
|
1379
1350
|
};
|
|
1380
1351
|
});
|
|
1381
1352
|
};
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
const
|
|
1385
|
-
if (
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
else lines.push(`${entry.path}: ${JSON.stringify(entry.value)}`);
|
|
1412
|
-
}
|
|
1413
|
-
return lines.join("\n");
|
|
1353
|
+
const inspectRawSurvey = (inputData, requestContext) => {
|
|
1354
|
+
const input = inputSchema$5.parse(inputData);
|
|
1355
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1356
|
+
if (!snapshot) return {
|
|
1357
|
+
ok: false,
|
|
1358
|
+
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1359
|
+
};
|
|
1360
|
+
return parseInspectionOutput(outputSchema$4, {
|
|
1361
|
+
ok: true,
|
|
1362
|
+
revision: snapshot.version.revision,
|
|
1363
|
+
contentHash: snapshot.version.contentHash,
|
|
1364
|
+
listOrdering: {
|
|
1365
|
+
"overview.topLevelKeys": "Alphabetical key order; not survey content order.",
|
|
1366
|
+
"overview.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1367
|
+
"overview.itemPaths": "Stored surveyItems array order. The numeric segment in each JSON Pointer path is its zero-based stored position; this differs from group-child display order.",
|
|
1368
|
+
values: "Same order as the requested JSON Pointer paths.",
|
|
1369
|
+
items: "Same order as the requested itemIdentifiers."
|
|
1370
|
+
},
|
|
1371
|
+
overview: input.paths || input.itemIdentifiers ? void 0 : buildOverview(snapshot),
|
|
1372
|
+
items: input.itemIdentifiers ? buildItemValues(snapshot, {
|
|
1373
|
+
itemIdentifiers: input.itemIdentifiers,
|
|
1374
|
+
valueCharacterLimit: input.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1375
|
+
}) : void 0,
|
|
1376
|
+
values: input.paths ? buildValues(snapshot, {
|
|
1377
|
+
paths: input.paths,
|
|
1378
|
+
textOffset: input.textOffset,
|
|
1379
|
+
valueCharacterLimit: input.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1380
|
+
}) : void 0
|
|
1381
|
+
}, "Raw-survey inspection");
|
|
1414
1382
|
};
|
|
1415
|
-
const surveyGetRawSurveyTool = createTool({
|
|
1416
|
-
id: TOOL_ID$5,
|
|
1417
|
-
description: "Inspect exact raw survey JSON by item id/key/fullKey or JSON Pointer path. Use this before survey-json-patch edits to inspect item config, survey-level metadata, maxItemsPerPage, templateValues, translations/locales, or group ordering arrays.",
|
|
1418
|
-
inputSchema: inputSchema$5,
|
|
1419
|
-
outputSchema: outputSchema$4,
|
|
1420
|
-
mcp: { annotations: {
|
|
1421
|
-
title: "Get raw survey JSON",
|
|
1422
|
-
readOnlyHint: true,
|
|
1423
|
-
destructiveHint: false,
|
|
1424
|
-
idempotentHint: true,
|
|
1425
|
-
openWorldHint: false
|
|
1426
|
-
} },
|
|
1427
|
-
execute: async (inputData, context) => {
|
|
1428
|
-
const input = {
|
|
1429
|
-
...inputData,
|
|
1430
|
-
valueCharacterLimit: inputData.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1431
|
-
};
|
|
1432
|
-
const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
|
|
1433
|
-
if (!snapshot) return {
|
|
1434
|
-
ok: false,
|
|
1435
|
-
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1436
|
-
};
|
|
1437
|
-
return {
|
|
1438
|
-
ok: true,
|
|
1439
|
-
revision: snapshot.version.revision,
|
|
1440
|
-
contentHash: snapshot.version.contentHash,
|
|
1441
|
-
listOrdering: {
|
|
1442
|
-
"overview.topLevelKeys": "Alphabetical key order; not survey content order.",
|
|
1443
|
-
"overview.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1444
|
-
"overview.itemPaths": "Stored surveyItems array order. The numeric segment in each JSON Pointer path is its zero-based stored position; this differs from group-child display order.",
|
|
1445
|
-
values: "Same order as the requested JSON Pointer paths.",
|
|
1446
|
-
items: "Same order as the requested itemIdentifiers."
|
|
1447
|
-
},
|
|
1448
|
-
overview: input.paths || input.itemIdentifiers ? void 0 : buildOverview(snapshot),
|
|
1449
|
-
items: input.itemIdentifiers ? buildItemValues(snapshot, {
|
|
1450
|
-
itemIdentifiers: input.itemIdentifiers,
|
|
1451
|
-
valueCharacterLimit: input.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1452
|
-
}) : void 0,
|
|
1453
|
-
values: input.paths ? buildValues(snapshot, {
|
|
1454
|
-
paths: input.paths,
|
|
1455
|
-
valueCharacterLimit: input.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1456
|
-
}) : void 0
|
|
1457
|
-
};
|
|
1458
|
-
},
|
|
1459
|
-
toModelOutput: (value) => toTextModelOutput(value, summarizeForModel$1)
|
|
1460
|
-
});
|
|
1461
1383
|
//#endregion
|
|
1462
|
-
//#region src/server/tools/survey-
|
|
1463
|
-
const TOOL_ID$4 = "survey_search_items";
|
|
1384
|
+
//#region src/server/tools/search-survey-items.ts
|
|
1464
1385
|
const MAX_QUERY_LENGTH = 180;
|
|
1465
1386
|
const MAX_MATCHES_PER_ITEM = 6;
|
|
1466
1387
|
const DEFAULT_TEXT_LIMIT = 220;
|
|
@@ -1621,59 +1542,27 @@ const searchItems = (snapshot, input) => {
|
|
|
1621
1542
|
};
|
|
1622
1543
|
}).filter((result) => Boolean(result)).sort((a, b) => b.score - a.score || a.item.fullKey.localeCompare(b.item.fullKey));
|
|
1623
1544
|
};
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1545
|
+
const searchSurveyItems = (inputData, requestContext) => {
|
|
1546
|
+
const input = inputSchema$4.parse(inputData);
|
|
1547
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1548
|
+
if (!snapshot) return {
|
|
1549
|
+
ok: false,
|
|
1550
|
+
reason: "No live editor snapshot was provided with this assistant turn.",
|
|
1551
|
+
query: input.query
|
|
1552
|
+
};
|
|
1553
|
+
const results = searchItems(snapshot, input);
|
|
1554
|
+
const page = sliceWithCursor(results, input);
|
|
1555
|
+
return parseInspectionOutput(outputSchema$3, {
|
|
1556
|
+
ok: true,
|
|
1557
|
+
query: input.query,
|
|
1558
|
+
totalAvailable: results.length,
|
|
1559
|
+
returned: page.items.length,
|
|
1560
|
+
nextCursor: page.nextCursor,
|
|
1561
|
+
truncated: page.truncated,
|
|
1562
|
+
ordering: "Results are ranked by descending text-match score, then item full key. Matches within each result are ranked by descending score. Neither order is survey or respondent-visible order.",
|
|
1563
|
+
results: page.items
|
|
1564
|
+
}, "Survey-item search");
|
|
1638
1565
|
};
|
|
1639
|
-
const surveySearchItemsTool = createTool({
|
|
1640
|
-
id: TOOL_ID$4,
|
|
1641
|
-
description: "Search all survey items by visible translated text, item keys, labels, and config identifiers. Use this when the user refers to an unselected item by title, label, option text, field label, or another phrase rather than an item id.",
|
|
1642
|
-
inputSchema: inputSchema$4,
|
|
1643
|
-
outputSchema: outputSchema$3,
|
|
1644
|
-
mcp: { annotations: {
|
|
1645
|
-
title: "Search survey items",
|
|
1646
|
-
readOnlyHint: true,
|
|
1647
|
-
destructiveHint: false,
|
|
1648
|
-
idempotentHint: true,
|
|
1649
|
-
openWorldHint: false
|
|
1650
|
-
} },
|
|
1651
|
-
execute: async (inputData, context) => {
|
|
1652
|
-
const input = {
|
|
1653
|
-
...inputData,
|
|
1654
|
-
limit: inputData.limit ?? 12
|
|
1655
|
-
};
|
|
1656
|
-
const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
|
|
1657
|
-
if (!snapshot) return {
|
|
1658
|
-
ok: false,
|
|
1659
|
-
reason: "No live editor snapshot was provided with this assistant turn.",
|
|
1660
|
-
query: input.query
|
|
1661
|
-
};
|
|
1662
|
-
const results = searchItems(snapshot, input);
|
|
1663
|
-
const page = sliceWithCursor(results, input);
|
|
1664
|
-
return {
|
|
1665
|
-
ok: true,
|
|
1666
|
-
query: input.query,
|
|
1667
|
-
totalAvailable: results.length,
|
|
1668
|
-
returned: page.items.length,
|
|
1669
|
-
nextCursor: page.nextCursor,
|
|
1670
|
-
truncated: page.truncated,
|
|
1671
|
-
ordering: "Results are ranked by descending text-match score, then item full key. Matches within each result are ranked by descending score. Neither order is survey or respondent-visible order.",
|
|
1672
|
-
results: page.items
|
|
1673
|
-
};
|
|
1674
|
-
},
|
|
1675
|
-
toModelOutput: (value) => toTextModelOutput(value, summarizeForModel)
|
|
1676
|
-
});
|
|
1677
1566
|
//#endregion
|
|
1678
1567
|
//#region src/server/tools/survey-inspect-tool.ts
|
|
1679
1568
|
const TOOL_ID$3 = "survey_inspect";
|
|
@@ -1715,6 +1604,7 @@ const inputSchema$3 = z.object({
|
|
|
1715
1604
|
cursor: optionalNonBlankString(240).describe("For current-context, search-items, or localization pagination: numeric cursor from a previous result."),
|
|
1716
1605
|
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."),
|
|
1717
1606
|
paths: optionalArrayInput(z.array(z.string().min(1).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. Empty means omitted.`),
|
|
1607
|
+
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."),
|
|
1718
1608
|
includeChildren: optionalInput(z.boolean()).describe("For item-details: include direct survey-tree children."),
|
|
1719
1609
|
includeItemConfig: optionalInput(z.boolean()).describe("For item-details: include choice/form/internal item config details."),
|
|
1720
1610
|
limit: optionalInput(z.number().int().min(1).max(50)).describe("Maximum list rows to return.")
|
|
@@ -1726,19 +1616,31 @@ const outputSchema$2 = z.object({
|
|
|
1726
1616
|
modelSummary: z.string(),
|
|
1727
1617
|
result: z.unknown().optional()
|
|
1728
1618
|
});
|
|
1729
|
-
const runTool = async (tool, input, context) => {
|
|
1730
|
-
const execute = tool.execute;
|
|
1731
|
-
if (!execute) throw new Error("Survey inspection backend is not executable.");
|
|
1732
|
-
return execute(input, context);
|
|
1733
|
-
};
|
|
1734
|
-
const unwrapToolOutput = (value) => {
|
|
1735
|
-
if (value && typeof value === "object" && "output" in value && value.output !== void 0) return value.output;
|
|
1736
|
-
return value;
|
|
1737
|
-
};
|
|
1738
1619
|
const stringifyForModel = (value) => {
|
|
1739
1620
|
if (typeof value === "string") return value;
|
|
1740
1621
|
return JSON.stringify(value, null, 2);
|
|
1741
1622
|
};
|
|
1623
|
+
const runInspectionBackend = (inspect) => {
|
|
1624
|
+
try {
|
|
1625
|
+
return {
|
|
1626
|
+
ok: true,
|
|
1627
|
+
value: inspect()
|
|
1628
|
+
};
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
if (!(error instanceof z.ZodError)) throw error;
|
|
1631
|
+
const firstIssue = error.issues[0];
|
|
1632
|
+
return {
|
|
1633
|
+
ok: false,
|
|
1634
|
+
reason: `Survey inspection validation failed${firstIssue?.path?.length ? ` at ${firstIssue.path.join(".").slice(0, 120)}` : ""}: ${(firstIssue?.message ?? "Invalid internal inspection data.").slice(0, 400)}`
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
const inspectionBackendFailure = (action, reason) => ({
|
|
1639
|
+
ok: false,
|
|
1640
|
+
action,
|
|
1641
|
+
reason,
|
|
1642
|
+
modelSummary: reason
|
|
1643
|
+
});
|
|
1742
1644
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1743
1645
|
const MAX_LOCALIZATION_TEXT_CHARS = 2e3;
|
|
1744
1646
|
const SURVEY_TRANSLATION_SURFACES = [
|
|
@@ -1763,6 +1665,7 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1763
1665
|
const addSurface = ({ contentKey, itemId, itemKey, itemType, sourceContent, surface, targetContent }) => {
|
|
1764
1666
|
const sourceText = truncateLocalizationText(sourceContent);
|
|
1765
1667
|
if (!sourceText) return;
|
|
1668
|
+
const sourcePath = `/translations/${encodeJsonPointerSegment(sourceLocale)}/${itemId ? `itemTranslations/${encodeJsonPointerSegment(itemId)}` : surface}/${encodeJsonPointerSegment(contentKey)}`;
|
|
1766
1669
|
surfaces.push({
|
|
1767
1670
|
scope: itemId ? "item" : "survey",
|
|
1768
1671
|
...surface ? { surface } : {},
|
|
@@ -1774,11 +1677,17 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1774
1677
|
contentKey,
|
|
1775
1678
|
sourceType: sourceContent.type,
|
|
1776
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` } : {},
|
|
1777
1684
|
...sourceContent.type === "richText" ? { sourceContent } : {},
|
|
1778
1685
|
targetPresent: Boolean(targetContent && truncateLocalizationText(targetContent)),
|
|
1779
1686
|
...targetContent ? {
|
|
1780
1687
|
targetType: targetContent.type,
|
|
1781
|
-
targetText: truncateLocalizationText(targetContent)
|
|
1688
|
+
targetText: truncateLocalizationText(targetContent),
|
|
1689
|
+
targetTextLength: getContentPlainText(targetContent).trim().length,
|
|
1690
|
+
targetTruncated: getContentPlainText(targetContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS
|
|
1782
1691
|
} : {}
|
|
1783
1692
|
});
|
|
1784
1693
|
};
|
|
@@ -1812,6 +1721,7 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1812
1721
|
sourceLocale,
|
|
1813
1722
|
targetLocale,
|
|
1814
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.",
|
|
1815
1725
|
totalSourceSurfaces: surfaces.length,
|
|
1816
1726
|
totalAvailable: filteredSurfaces.length,
|
|
1817
1727
|
missingCount,
|
|
@@ -1903,7 +1813,7 @@ const listAssets = (input, snapshot) => {
|
|
|
1903
1813
|
};
|
|
1904
1814
|
const surveyInspectTool = createTool({
|
|
1905
1815
|
id: TOOL_ID$3,
|
|
1906
|
-
description: "Inspect the current request-scoped survey draft through one compact interface: current context, item search, item details, paged source/target localization surfaces, image assets with explicit asset IDs and usages, or focused raw JSON. At turn start the draft equals the live snapshot; successful same-turn edits appear immediately.",
|
|
1816
|
+
description: "Inspect the current request-scoped survey draft through one compact interface: current context, item search, item details, paged source/target 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.",
|
|
1907
1817
|
inputSchema: inputSchema$3,
|
|
1908
1818
|
outputSchema: outputSchema$2,
|
|
1909
1819
|
mcp: { annotations: {
|
|
@@ -1915,7 +1825,8 @@ const surveyInspectTool = createTool({
|
|
|
1915
1825
|
} },
|
|
1916
1826
|
execute: async (inputData, context) => {
|
|
1917
1827
|
const input = inputData;
|
|
1918
|
-
const
|
|
1828
|
+
const requestContext = context?.requestContext;
|
|
1829
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1919
1830
|
if (!snapshot) return {
|
|
1920
1831
|
ok: false,
|
|
1921
1832
|
action: input.action,
|
|
@@ -1923,14 +1834,16 @@ const surveyInspectTool = createTool({
|
|
|
1923
1834
|
modelSummary: "No survey draft is available for this turn."
|
|
1924
1835
|
};
|
|
1925
1836
|
if (input.action === "current-context") {
|
|
1926
|
-
const
|
|
1837
|
+
const inspection = runInspectionBackend(() => inspectSurveyCurrentContext({
|
|
1927
1838
|
scopes: input.scopes ?? ["summary", "selected-item"],
|
|
1928
1839
|
limit: input.limit,
|
|
1929
1840
|
cursor: input.cursor,
|
|
1930
1841
|
itemId: resolveItemRef(input, snapshot),
|
|
1931
1842
|
query: input.query,
|
|
1932
1843
|
outlineMode: input.outlineMode
|
|
1933
|
-
},
|
|
1844
|
+
}, requestContext));
|
|
1845
|
+
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
1846
|
+
const result = inspection.value;
|
|
1934
1847
|
return {
|
|
1935
1848
|
ok: true,
|
|
1936
1849
|
action: input.action,
|
|
@@ -1945,11 +1858,14 @@ const surveyInspectTool = createTool({
|
|
|
1945
1858
|
reason: "Search requires a query.",
|
|
1946
1859
|
modelSummary: "Search requires a query."
|
|
1947
1860
|
};
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1861
|
+
const query = input.query;
|
|
1862
|
+
const inspection = runInspectionBackend(() => searchSurveyItems({
|
|
1863
|
+
query,
|
|
1950
1864
|
limit: input.limit,
|
|
1951
1865
|
cursor: input.cursor
|
|
1952
|
-
},
|
|
1866
|
+
}, requestContext));
|
|
1867
|
+
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
1868
|
+
const result = inspection.value;
|
|
1953
1869
|
return {
|
|
1954
1870
|
ok: true,
|
|
1955
1871
|
action: input.action,
|
|
@@ -1965,12 +1881,14 @@ const surveyInspectTool = createTool({
|
|
|
1965
1881
|
reason: "No item reference was provided and no item is selected.",
|
|
1966
1882
|
modelSummary: "No item reference was provided and no item is selected."
|
|
1967
1883
|
};
|
|
1968
|
-
const
|
|
1884
|
+
const inspection = runInspectionBackend(() => inspectSurveyItemDetails({
|
|
1969
1885
|
itemId,
|
|
1970
1886
|
includeSurveyChildren: input.includeChildren ?? false,
|
|
1971
1887
|
includeItemConfig: input.includeItemConfig ?? true,
|
|
1972
1888
|
childrenLimit: input.limit
|
|
1973
|
-
},
|
|
1889
|
+
}, requestContext));
|
|
1890
|
+
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
1891
|
+
const result = inspection.value;
|
|
1974
1892
|
return {
|
|
1975
1893
|
ok: true,
|
|
1976
1894
|
action: input.action,
|
|
@@ -1982,17 +1900,29 @@ const surveyInspectTool = createTool({
|
|
|
1982
1900
|
const allDiagnostics = analyzeSurveyAssistantExpressionReferences(snapshot.survey, snapshot.trustedCapabilities);
|
|
1983
1901
|
const itemId = input.itemRef ? resolveItemRef(input, snapshot) : void 0;
|
|
1984
1902
|
const diagnostics = itemId ? allDiagnostics.filter((diagnostic) => diagnostic.ownerItemId === itemId) : allDiagnostics;
|
|
1903
|
+
const orderedDiagnostics = [...diagnostics].sort((left, right) => Number(isBlockingSurveyExpressionReferenceDiagnostic(right)) - Number(isBlockingSurveyExpressionReferenceDiagnostic(left)));
|
|
1904
|
+
const blockingCount = diagnostics.filter(isBlockingSurveyExpressionReferenceDiagnostic).length;
|
|
1905
|
+
const advisoryCount = diagnostics.length - blockingCount;
|
|
1985
1906
|
const limit = input.limit ?? 50;
|
|
1907
|
+
const limitedDiagnostics = orderedDiagnostics.slice(0, limit);
|
|
1908
|
+
const guidanceByDispositionAndCategory = {};
|
|
1909
|
+
for (const diagnostic of orderedDiagnostics) {
|
|
1910
|
+
const byCategory = guidanceByDispositionAndCategory[diagnostic.disposition] ??= {};
|
|
1911
|
+
byCategory[diagnostic.category] = getSurveyAssistantExpressionDiagnosticGuidance(diagnostic);
|
|
1912
|
+
}
|
|
1986
1913
|
const result = {
|
|
1987
|
-
valid:
|
|
1914
|
+
valid: blockingCount === 0,
|
|
1988
1915
|
count: diagnostics.length,
|
|
1989
|
-
|
|
1916
|
+
blockingCount,
|
|
1917
|
+
advisoryCount,
|
|
1918
|
+
diagnostics: limitedDiagnostics,
|
|
1919
|
+
guidanceByDispositionAndCategory,
|
|
1990
1920
|
truncated: diagnostics.length > limit
|
|
1991
1921
|
};
|
|
1992
1922
|
return {
|
|
1993
1923
|
ok: true,
|
|
1994
1924
|
action: input.action,
|
|
1995
|
-
modelSummary: diagnostics.length === 0 ? "No
|
|
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)}`,
|
|
1996
1926
|
result
|
|
1997
1927
|
};
|
|
1998
1928
|
}
|
|
@@ -2030,10 +1960,13 @@ const surveyInspectTool = createTool({
|
|
|
2030
1960
|
const itemIdentifiers = itemId ? [itemId] : void 0;
|
|
2031
1961
|
const requestedPaths = itemIdentifiers ? void 0 : input.paths;
|
|
2032
1962
|
const boundedPaths = requestedPaths?.slice(0, MAX_RAW_JSON_PATHS_PER_RESULT);
|
|
2033
|
-
const
|
|
1963
|
+
const rawInspection = runInspectionBackend(() => inspectRawSurvey({
|
|
2034
1964
|
paths: boundedPaths,
|
|
2035
|
-
itemIdentifiers
|
|
2036
|
-
|
|
1965
|
+
itemIdentifiers,
|
|
1966
|
+
textOffset: input.textOffset
|
|
1967
|
+
}, requestContext));
|
|
1968
|
+
if (!rawInspection.ok) return inspectionBackendFailure(input.action, rawInspection.reason);
|
|
1969
|
+
const rawResult = rawInspection.value;
|
|
2037
1970
|
const ignoredPathCount = itemIdentifiers ? input.paths?.length ?? 0 : 0;
|
|
2038
1971
|
const omittedPathCount = Math.max(0, (requestedPaths?.length ?? 0) - (boundedPaths?.length ?? 0));
|
|
2039
1972
|
const requestCoverage = ignoredPathCount > 0 || omittedPathCount > 0 ? {
|
|
@@ -2204,7 +2137,7 @@ const formatAuditForModel = (output) => {
|
|
|
2204
2137
|
finding.locale,
|
|
2205
2138
|
finding.contentKey
|
|
2206
2139
|
].filter(Boolean).join(":");
|
|
2207
|
-
return `- ${finding.code}${location ? ` (${location})` : ""}: ${previewAuditMessage(finding.message)}`;
|
|
2140
|
+
return `- [${finding.severity}] ${finding.code}${location ? ` (${location})` : ""}: ${previewAuditMessage(finding.message)}${finding.suggestion ? ` Guidance: ${previewAuditMessage(finding.suggestion)}` : ""}`;
|
|
2208
2141
|
}).join("\n")}` : void 0,
|
|
2209
2142
|
report.qualityReviewPrinciples.length > 0 ? `Qualitative review rubric:\n${report.qualityReviewPrinciples.map((principle) => `- ${principle.id}: ${principle.prompt}`).join("\n")}` : void 0,
|
|
2210
2143
|
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) => {
|
|
@@ -2244,6 +2177,7 @@ const surveyAuditTool = createTool({
|
|
|
2244
2177
|
//#region src/server/tools/survey-asset-tool.ts
|
|
2245
2178
|
const TOOL_ID = "survey_asset";
|
|
2246
2179
|
const inputSchema = z.object({
|
|
2180
|
+
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
2247
2181
|
action: z.enum([
|
|
2248
2182
|
"import-attachment",
|
|
2249
2183
|
"update-metadata",
|
|
@@ -2293,12 +2227,13 @@ const summarizeAssetChange = (event) => {
|
|
|
2293
2227
|
lines.push(`Imported: ${event.operations.filter((operation) => operation.kind === "set-image-asset").map(describeAssetOperation).join(", ")}`);
|
|
2294
2228
|
lines.push(`Asset id to reference: ${importedAssetIds.join(", ")}`);
|
|
2295
2229
|
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.");
|
|
2296
|
-
lines.push(`New info-item image source: { "type": "asset", "assetId": "${importedAssetIds[0]}" }. Embed
|
|
2297
|
-
lines.push(`New info-item translation example: { "locale": "<selected-locale>", "contentKey": "content", "
|
|
2230
|
+
lines.push(`New info-item image source: { "type": "asset", "assetId": "${importedAssetIds[0]}" }. Embed the canonical image block inside the create-item translation richTextBlocks array.`);
|
|
2231
|
+
lines.push(`New info-item translation example: { "locale": "<selected-locale>", "contentKey": "content", "richTextBlocks": [{ "type": "image", "source": { "type": "asset", "assetId": "${importedAssetIds[0]}" }, "alt": "<meaningful description>" }] }.`);
|
|
2298
2232
|
}
|
|
2299
2233
|
}
|
|
2300
2234
|
if (event.validation.issues.length > 0) lines.push(`Issues:\n${event.validation.issues.map((issue) => `- ${issue.code}: ${issue.message}`).join("\n")}`);
|
|
2301
2235
|
if (event.validation.warnings.length > 0) lines.push(`Warnings:\n${event.validation.warnings.map((warning) => `- ${warning.code}: ${warning.message}`).join("\n")}`);
|
|
2236
|
+
lines.push(...summarizePendingRepairs(event));
|
|
2302
2237
|
return lines.join("\n");
|
|
2303
2238
|
};
|
|
2304
2239
|
const redactEmbeddedImageOperation = (operation) => operation.kind === "set-image-asset" ? {
|
|
@@ -2408,9 +2343,10 @@ const surveyAssetTool = createTool({
|
|
|
2408
2343
|
normalizedOperations: []
|
|
2409
2344
|
}
|
|
2410
2345
|
});
|
|
2411
|
-
if (event.status === "rejected") return turnDraft.reject(event, [
|
|
2346
|
+
if (event.status === "rejected") return turnDraft.reject(event, collectSurveyOperationRecoveryTargets([operation], snapshot));
|
|
2412
2347
|
}
|
|
2413
2348
|
return turnDraft.stage({
|
|
2349
|
+
retryOfChangeIds: input.retryOfChangeIds,
|
|
2414
2350
|
operations: [operation],
|
|
2415
2351
|
snapshot,
|
|
2416
2352
|
summary: input.summary
|
|
@@ -2423,18 +2359,18 @@ const surveyAssetTool = createTool({
|
|
|
2423
2359
|
//#region src/server/tools/index.ts
|
|
2424
2360
|
const SURVEY_ASSISTANT_TOOL_IDS = [
|
|
2425
2361
|
TOOL_ID$3,
|
|
2426
|
-
TOOL_ID$
|
|
2427
|
-
TOOL_ID$
|
|
2428
|
-
TOOL_ID$
|
|
2362
|
+
TOOL_ID$5,
|
|
2363
|
+
TOOL_ID$4,
|
|
2364
|
+
TOOL_ID$6,
|
|
2429
2365
|
TOOL_ID$2,
|
|
2430
2366
|
TOOL_ID$1,
|
|
2431
2367
|
TOOL_ID
|
|
2432
2368
|
];
|
|
2433
2369
|
const surveyAssistantTools = Object.freeze({
|
|
2434
2370
|
[TOOL_ID$3]: surveyInspectTool,
|
|
2435
|
-
[TOOL_ID$
|
|
2436
|
-
[TOOL_ID$
|
|
2437
|
-
[TOOL_ID$
|
|
2371
|
+
[TOOL_ID$5]: surveyChangeTool,
|
|
2372
|
+
[TOOL_ID$6]: surveyAuthoringReferenceTool,
|
|
2373
|
+
[TOOL_ID$4]: surveyExpressionTool,
|
|
2438
2374
|
[TOOL_ID$2]: surveyAttachmentTool,
|
|
2439
2375
|
[TOOL_ID$1]: surveyAuditTool,
|
|
2440
2376
|
[TOOL_ID]: surveyAssetTool
|
|
@@ -2442,4 +2378,4 @@ const surveyAssistantTools = Object.freeze({
|
|
|
2442
2378
|
//#endregion
|
|
2443
2379
|
export { surveyAuditTool as i, surveyAssistantTools as n, surveyAssetTool as r, SURVEY_ASSISTANT_TOOL_IDS as t };
|
|
2444
2380
|
|
|
2445
|
-
//# sourceMappingURL=tools-
|
|
2381
|
+
//# sourceMappingURL=tools-BvSovehT.mjs.map
|