@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.
Files changed (94) hide show
  1. package/README.md +9 -4
  2. package/dist/{authoring-references-CW38pYzK.mjs → authoring-references-BJQ_1nMX.mjs} +2 -2
  3. package/dist/authoring-references-BJQ_1nMX.mjs.map +1 -0
  4. package/dist/capabilities-1KNnNO1_.mjs +73 -0
  5. package/dist/capabilities-1KNnNO1_.mjs.map +1 -0
  6. package/dist/capabilities-default.d.mts +1 -1
  7. package/dist/capabilities-default.d.mts.map +1 -1
  8. package/dist/capabilities-default.mjs +22 -25
  9. package/dist/capabilities-default.mjs.map +1 -1
  10. package/dist/{constants-BMGTgfjv.d.mts → constants-Be91Gay9.d.mts} +3 -3
  11. package/dist/{constants-BMGTgfjv.d.mts.map → constants-Be91Gay9.d.mts.map} +1 -1
  12. package/dist/{controller-proxy-DMTqer6T.mjs → controller-proxy-BH05e-C2.mjs} +21 -8
  13. package/dist/controller-proxy-BH05e-C2.mjs.map +1 -0
  14. package/dist/{controller-proxy-mRvez2qw.d.mts → controller-proxy-Bd0ZHBPF.d.mts} +11 -11
  15. package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +1 -0
  16. package/dist/{engine-CQwOAcwK.mjs → engine-Bo85Oyj0.mjs} +2985 -2347
  17. package/dist/engine-Bo85Oyj0.mjs.map +1 -0
  18. package/dist/engine.d.mts +2 -2
  19. package/dist/engine.mjs +3 -3
  20. package/dist/{index-D6nqkz0O.d.mts → index-Tnqv-yKW.d.mts} +56 -71
  21. package/dist/index-Tnqv-yKW.d.mts.map +1 -0
  22. package/dist/index-l_EK4yK7.d.mts +404 -0
  23. package/dist/index-l_EK4yK7.d.mts.map +1 -0
  24. package/dist/{index-DvTqML-0.d.mts → index-nhrgbgO5.d.mts} +19 -5
  25. package/dist/index-nhrgbgO5.d.mts.map +1 -0
  26. package/dist/{index-B0lxsAzx.d.mts → index-x0qprHgK.d.mts} +19 -7
  27. package/dist/index-x0qprHgK.d.mts.map +1 -0
  28. package/dist/{lifecycle-BmGAIUlv.d.mts → lifecycle-Bx0aq_4S.d.mts} +7 -1
  29. package/dist/{lifecycle-BmGAIUlv.d.mts.map → lifecycle-Bx0aq_4S.d.mts.map} +1 -1
  30. package/dist/{memory-thread-repository-CjFGVNZh.mjs → memory-thread-repository-KWHQWFEW.mjs} +43 -12
  31. package/dist/memory-thread-repository-KWHQWFEW.mjs.map +1 -0
  32. package/dist/{protocol-DJUP4gc0.mjs → protocol-BhzQbx81.mjs} +66 -16
  33. package/dist/protocol-BhzQbx81.mjs.map +1 -0
  34. package/dist/protocol.d.mts +3 -3
  35. package/dist/protocol.mjs +2 -2
  36. package/dist/react-B3ybjJ-X.mjs +1620 -0
  37. package/dist/react-B3ybjJ-X.mjs.map +1 -0
  38. package/dist/react-integration.d.mts +1 -1
  39. package/dist/react-integration.mjs +1 -1
  40. package/dist/react.d.mts +3 -3
  41. package/dist/react.mjs +3 -3
  42. package/dist/references/assistant-operations.md +45 -39
  43. package/dist/references/expressions.md +28 -0
  44. package/dist/references/item-types.md +1 -1
  45. package/dist/references/localization.md +5 -4
  46. package/dist/references/rich-text-content.md +60 -27
  47. package/dist/references/source-material-surveys.md +9 -2
  48. package/dist/references/survey-data-model.md +9 -1
  49. package/dist/{request-context-BjTHNcDd.mjs → request-context-CG4SWijt.mjs} +3 -3
  50. package/dist/{request-context-BjTHNcDd.mjs.map → request-context-CG4SWijt.mjs.map} +1 -1
  51. package/dist/server-agent.d.mts +19 -8
  52. package/dist/server-agent.d.mts.map +1 -1
  53. package/dist/server-agent.mjs +7 -11
  54. package/dist/server-agent.mjs.map +1 -1
  55. package/dist/server-runtime.d.mts +19 -13
  56. package/dist/server-runtime.d.mts.map +1 -1
  57. package/dist/server-runtime.mjs +7 -2
  58. package/dist/server-runtime.mjs.map +1 -1
  59. package/dist/server-tasks.d.mts +1 -1
  60. package/dist/server-tasks.mjs +2 -2
  61. package/dist/server-tools.d.mts +1 -1
  62. package/dist/server-tools.mjs +1 -1
  63. package/dist/server.d.mts +5 -5
  64. package/dist/server.mjs +3 -3
  65. package/dist/{tasks-DxzD25Zc.d.mts → tasks-CYqrcaRO.d.mts} +4 -4
  66. package/dist/{tasks-DxzD25Zc.d.mts.map → tasks-CYqrcaRO.d.mts.map} +1 -1
  67. package/dist/{thread-handlers-AbQbt4sX.d.mts → thread-handlers-DBAaU7BA.d.mts} +6 -4
  68. package/dist/thread-handlers-DBAaU7BA.d.mts.map +1 -0
  69. package/dist/{tools-CFh35R4d.mjs → tools-BvSovehT.mjs} +431 -495
  70. package/dist/tools-BvSovehT.mjs.map +1 -0
  71. package/dist/ui.css +1 -1
  72. package/dist/ui.d.mts +5 -4
  73. package/dist/ui.d.mts.map +1 -1
  74. package/dist/ui.mjs +619 -66
  75. package/dist/ui.mjs.map +1 -1
  76. package/docs/integration.md +40 -0
  77. package/package.json +21 -19
  78. package/dist/authoring-references-CW38pYzK.mjs.map +0 -1
  79. package/dist/controller-proxy-DMTqer6T.mjs.map +0 -1
  80. package/dist/controller-proxy-mRvez2qw.d.mts.map +0 -1
  81. package/dist/engine-CQwOAcwK.mjs.map +0 -1
  82. package/dist/form-authoring-kQDgC5oz.mjs +0 -155
  83. package/dist/form-authoring-kQDgC5oz.mjs.map +0 -1
  84. package/dist/index-B0lxsAzx.d.mts.map +0 -1
  85. package/dist/index-BS-Xjk1h.d.mts +0 -224
  86. package/dist/index-BS-Xjk1h.d.mts.map +0 -1
  87. package/dist/index-D6nqkz0O.d.mts.map +0 -1
  88. package/dist/index-DvTqML-0.d.mts.map +0 -1
  89. package/dist/memory-thread-repository-CjFGVNZh.mjs.map +0 -1
  90. package/dist/protocol-DJUP4gc0.mjs.map +0 -1
  91. package/dist/react-Dqlmasrs.mjs +0 -768
  92. package/dist/react-Dqlmasrs.mjs.map +0 -1
  93. package/dist/thread-handlers-AbQbt4sX.d.mts.map +0 -1
  94. 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-DJUP4gc0.mjs";
2
- import { d as getDeterministicCreateItemTypes, f as inspectAssistantItem, p as resolveAssistantCapabilityReference } from "./form-authoring-kQDgC5oz.mjs";
3
- import { A as getKeyIndexEntries, F as itemSummarySchema, G as summarizeKeyIndexEntries, H as sliceWithCursor, I as mapKeyIndexById, J as summarizeRawItem, L as mapOutlineById, M as getRawSurveyItems, N as itemConfigDetailsSchema, P as itemMatchesQuery, R as mapRawItemsById, T as findDirectChildrenByFullKey, U as summarizeFocusItemNode, V as resolveItemSummary, W as summarizeItemById, X as unique, Y as translationEntrySchema, Z as analyzeSurveyAssistantExpressionReferences, b as buildItemConfigDetails, f as normalizeSurveyChangeOperations, i as auditSurvey, j as getRawSurvey, l as createSurveyChangeSet, m as expressionMutationSchema, p as buildExpressionMutationPatches, q as summarizeOutlineNode, v as buildConfigSearchText, w as clampLimit, x as buildItemTranslationEntries, y as buildEmbeddedFormDetails, z as normalizeText } from "./engine-CQwOAcwK.mjs";
4
- import { r as getSurveyAssistantEngineContext, s as requireSurveyAssistantTurnState } from "./request-context-BjTHNcDd.mjs";
5
- import { a as surveyAuthoringReferences, i as surveyAuthoringReferenceTopics, r as getBuiltInItemTypeAuthoringReference, t as builtInSurveyAuthoringItemTypes } from "./authoring-references-CW38pYzK.mjs";
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 a dedicated reference only when needed: ${manifest.referenceTopics.join(", ")}.` : void 0
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 enforced when the",
80
- "proposal is validated. The tool may normalize limited schema-near shorthand, but prefer this",
81
- "canonical form.",
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$1 = (value) => value && typeof value === "object" && "output" in value && value.output !== void 0 ? value.output : value;
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$1(value);
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$10 = "survey_authoring_reference";
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$10,
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 entry = focusedBuiltInItemTypeReference ?? capabilityEntry ?? builtInEntry;
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$9 = "survey_change";
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
- "Create items with { kind: \"create-item\", target: { parentItemId, index? }, item: { id?, key, itemType, config }, translations? }. Put respondent content in top-level translations and item settings in item.config.",
295
- "Use update-item-translation with itemId, locale, contentKey, and plainText for ordinary text or content for a complete structured value. Item titles and option/field labels are translations, not item labels.",
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 placement that already exists in the live survey or validated turn draft.",
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
- "Raw fallback is one { kind: \"survey-json-patch\", patches } operation containing all related survey-rooted edits; it must be the only operation in the call.",
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(1).max(200).describe(OPERATIONS_DESCRIPTION)
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$9,
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 operationsParse = z.array(surveyOperationSchema).safeParse(normalizeSurveyChangeOperations(inputData.operations, defaultLocale));
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$8 = "survey_expression";
451
- const contextActionSchema = z.object({
452
- action: z.literal("context"),
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: z.array(expressionMutationSchema).min(1).max(40)
497
+ mutations: expressionMutationListSchema
461
498
  });
462
499
  const inputSchema$8 = z.object({
463
- action: z.enum(["context", "prepare"]).describe("Choose exactly one envelope. Inspect with { \"action\": \"context\", \"itemIds\"?: [...], \"includeRawLocations\"?: true, \"reason\"?: \"...\" }; omit summary and mutations. Create edits with { \"action\": \"prepare\", \"summary\": \"...\", \"mutations\": [...] }; omit reason, itemIds, and includeRawLocations."),
464
- reason: z.string().max(500).optional(),
465
- itemIds: z.array(z.string().min(1).max(240)).max(12).optional().describe("Context action only: optional item ids/keys to focus existing expression locations. Omit for prepare."),
466
- includeRawLocations: z.boolean().optional().describe("Context action only: include existing expression locations by raw item. Omit for prepare."),
467
- summary: z.string().min(1).max(500).optional().describe("Prepare action only: required user-facing summary. Omit for context."),
468
- mutations: z.array(z.record(z.string(), z.unknown())).min(1).max(40).optional().describe("Prepare action only: required expression mutation objects using kebab-case kind names; omit this field for context. Item visibility shape: { kind: \"set-item-display-condition\", itemId: \"<existing-item-id>\", expression: { type: \"function\", functionName: \"eq\", arguments: [...] } }. expression is always CASE expression JSON, never a string or expression-language snippet. Other supported kinds set/clear component display/disabled conditions, validation expressions, prefills, and template values.")
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 !== "prepare") return;
471
- if (!value.summary) ctx.addIssue({
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: "At least one mutation is required for prepare."
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 responseSlotTypes = buildResponseSlotTypes(snapshot);
553
- const responseSlots = Object.entries(responseSlotTypes).map(([ref, valueType]) => ({
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 itemFilter = input.action === "context" && input.itemIds?.length ? new Set(input.itemIds) : void 0;
558
- const expressionLocations = input.action === "context" && input.includeRawLocations === true ? rawItems.map((item, rawIndex) => {
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 slots (${responseSlots.length}): ${responseSlots.slice(0, 80).map((slot) => `${slot.ref}${slot.valueType ? `:${slot.valueType}` : ""}`).join(", ")}`,
586
- responseSlots.length > 80 ? "Response slot list truncated in summary; inspect context result for more." : void 0,
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$8,
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
- const prepareParse = prepareActionSchema.safeParse(inputData);
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 = [...new Set(prepareInput.mutations.map(expressionMutationTarget))];
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
- }), addressedTargets);
671
- if (patches.length === 0) return reject(rejectedEvent({
672
- summary: prepareInput.summary,
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
- message: "No expression patches were produced. Check whether the requested locations already match the target state."
677
- })],
678
- warnings
679
- }), addressedTargets);
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/survey-get-current-context-tool.ts
704
- const TOOL_ID$7 = "survey_get_current_context";
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(160).optional().describe("Optional text used to find matching item ids, keys, labels, or response slots."),
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 summarizeForModel$3 = (output) => {
966
- if (!output.ok) return output.reason ?? "Current editor context is unavailable.";
967
- const lines = [`Snapshot revision ${output.revision}, content hash ${output.contentHash}.`];
968
- if (output.summary) {
969
- const selected = output.summary.selectedItemId ?? "none";
970
- const surveyKey = output.summary.surveyKey ?? "not set";
971
- lines.push(`Summary: view=${output.summary.view}; selectedItemId=${selected}; locale=${output.summary.selectedLocale}; surveyKey=${surveyKey}; itemCount=${output.summary.itemCount} (excluding the structural root group).`);
972
- if (output.summary.rootItem) lines.push(`Root group for top-level create-item target.parentItemId: ${output.summary.rootItem.fullKey} (id=${output.summary.rootItem.itemId}, key=${output.summary.rootItem.key}).`);
973
- lines.push(`Outline access: ${output.summary.outlineItemCount} item(s), including the structural root group, are pageable.`);
974
- const creatableDetails = output.summary.itemTypeDetails.filter((entry) => entry.create === "deterministic").map((entry) => {
975
- const primarySurface = entry.contentSurfaces.find((surface) => surface.role === "primary");
976
- const fixedSurfaces = entry.contentSurfaces.filter((surface) => !surface.pattern).map((surface) => surface.key);
977
- const patterns = entry.contentSurfaces.filter((surface) => surface.pattern).map((surface) => surface.key);
978
- const contentKeys = [
979
- primarySurface ? `primary=${primarySurface.key}` : void 0,
980
- fixedSurfaces.length ? `visible=${fixedSurfaces.join("|")}` : void 0,
981
- patterns.length ? `dynamic=${patterns.join("|")}` : void 0,
982
- entry.authoring?.compactHint
983
- ].filter((value) => Boolean(value)).join("; ");
984
- return contentKeys ? `${entry.itemType} (${contentKeys})` : entry.itemType;
985
- });
986
- if (creatableDetails.length > 0) lines.push(`Creatable item types: ${creatableDetails.join(", ")}.`);
987
- }
988
- if (output.selectedItem) if (output.selectedItem.available && output.selectedItem.item) {
989
- lines.push(`Selected item: ${output.selectedItem.item.fullKey} (${output.selectedItem.item.itemType})${output.selectedItem.item.itemLabel ? ` - ${output.selectedItem.item.itemLabel}` : ""}.`);
990
- if (output.selectedItem.translationSnippets?.length) lines.push(`Translation snippets: ${output.selectedItem.translationSnippets.map((entry) => `${entry.locale}.${entry.contentKey}: ${entry.text}`).join("; ")}`);
991
- } else lines.push(`Selected item unavailable: ${output.selectedItem.reason ?? "unknown reason"}.`);
992
- if (output.outline) {
993
- lines.push(`Outline (${output.outline.strategy}${output.outline.mode ? `, mode=${output.outline.mode}` : ""}): ${output.outline.returned}/${output.outline.totalAvailable} rows returned.`);
994
- for (const item of output.outline.items) lines.push(`- ${item.fullKey} (${item.itemType})${item.itemLabel ? ` - ${item.itemLabel}` : ""}`);
995
- lines.push(`Outline order: ${output.outline.ordering}`);
996
- if (output.outline.nextCursor) lines.push(`More outline rows are available with cursor ${output.outline.nextCursor}.`);
997
- }
998
- if (output.responseSlots) {
999
- lines.push(output.responseSlots.available ? `Response slots: ${output.responseSlots.slots.join(", ") || "none"}` : `Response slots unavailable: ${output.responseSlots.note ?? "not indexed"}.`);
1000
- if (output.responseSlots.available) lines.push(`Response-slot order: ${output.responseSlots.ordering}`);
1001
- }
1002
- if (output.uiContext) lines.push(`UI context: activeSurface=${output.uiContext.activeSurface ?? "not provided"}; openPanels=${output.uiContext.openPanels.join(", ") || "none"}; highlights=${output.uiContext.highlights.map((entry) => entry.label ?? entry.itemId ?? entry.componentId ?? entry.kind).join(", ") || "none"}.`);
1003
- if (output.history) lines.push(`Recent editor history (${output.history.currentIndex + 1}/${output.history.totalEntries}): ${output.history.recent.map((entry) => `${entry.index}:${entry.label}${entry.isCurrent ? " [current]" : ""}`).join("; ") || "none"}.`);
1004
- return lines.join("\n");
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/survey-get-item-details-tool.ts
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(MAX_TEXT_LIMIT).default(DEFAULT_TEXT_LIMIT$1).describe("Maximum characters returned per translation snippet."),
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(DEFAULT_CHILDREN_LIMIT).describe("Maximum number of direct survey-tree children to return.")
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 summarizeConfigDetails = (configDetails) => {
1103
- const lines = [`Config details (${configDetails.kind}): ${configDetails.summary.join("; ")}`];
1104
- if (configDetails.mainResponseSlotId) lines.push(`Main response slot: ${configDetails.mainResponseSlotId}`);
1105
- for (const option of configDetails.choiceOptions ?? []) {
1106
- lines.push(`- ${option.position}. option ${option.key ?? option.id} (id=${option.id}, labelContentKey=${option.labelContentKey})`);
1107
- if (option.embeddedFormItemId || option.embeddedFormFieldGroupCount) {
1108
- lines.push(` embedded form: itemId=${option.embeddedFormItemId ?? "inline"}, fieldGroups=${option.embeddedFormFieldGroupCount ?? 0}, authoringMode=${option.embeddedFormAuthoringMode ?? "custom"}, simpleModeValid=${option.embeddedFormSimpleModeValid ?? true}`);
1109
- if (option.embeddedFormTranslationPrefix || option.embeddedFormResponseSlotPrefix) lines.push(` embedded form keys: translationPrefix=${option.embeddedFormTranslationPrefix ?? "n/a"}, responseSlotPrefix=${option.embeddedFormResponseSlotPrefix ?? "n/a"}`);
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
- for (const group of configDetails.formFieldGroups ?? []) {
1113
- lines.push(`- ${group.position}. field group ${group.id}: ${group.fieldCount} field(s), titleContentKey=${group.titleContentKey}, layout=${group.layout.base}/${group.layout.xs}/${group.layout.sm}/${group.layout.md}/${group.layout.xl}`);
1114
- if (group.hasDisplayCondition !== void 0) lines.push(` hasDisplayCondition=${group.hasDisplayCondition ? "true" : "false"}`);
1115
- for (const field of group.fields) {
1116
- lines.push(` - ${field.position}. field ${field.key} (${field.type}, id=${field.id}, responseSlot=${field.responseSlotId}, labelContentKey=${field.labelContentKey})`);
1117
- lines.push(` extra content keys: description=${field.descriptionContentKey}, error=${field.errorContentKey}${field.placeholderContentKey ? `, placeholder=${field.placeholderContentKey}` : ""}${field.unitContentKey ? `, unit=${field.unitContentKey}` : ""}${field.checkedLabelContentKey ? `, checkedLabel=${field.checkedLabelContentKey}` : ""}${field.uncheckedLabelContentKey ? `, uncheckedLabel=${field.uncheckedLabelContentKey}` : ""}`);
1118
- if (field.validationTypes?.length) lines.push(` validations: ${field.validationTypes.join(", ")}`);
1119
- if (field.validationMessageContentKeys?.length) lines.push(` validation message keys: ${field.validationMessageContentKeys.join(", ")}`);
1120
- if (field.selectOptions?.length) lines.push(` select options: ${field.selectOptions.map((option) => `${option.position}. ${option.value ?? option.id} (${option.labelContentKey})`).join(", ")}`);
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 lines;
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/survey-get-raw-survey-tool.ts
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(getValueAtJsonPointer(rawSurvey, path), input.valueCharacterLimit)
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 summarizeForModel$1 = (output) => {
1383
- if (!output.ok) return output.reason ?? "Raw survey context is unavailable.";
1384
- const lines = [];
1385
- if (output.overview) {
1386
- lines.push(`Raw survey overview: ${output.overview.surveyItemCount} item(s), top-level keys ${output.overview.topLevelKeys.join(", ")}.`);
1387
- lines.push(`Locales: ${output.overview.locales.join(", ") || "none"}.`);
1388
- lines.push("Survey item JSON paths:");
1389
- for (const item of output.overview.itemPaths.slice(0, 50)) lines.push(`- ${item.path}: ${item.key} (${item.itemType}, id=${item.id})${item.parentId ? ` parent=${item.parentId}` : ""}`);
1390
- if (output.overview.itemPaths.length > 50) lines.push("More item paths exist; request a specific path for exact raw JSON.");
1391
- }
1392
- if (output.listOrdering) for (const [list, meaning] of Object.entries(output.listOrdering)) lines.push(`List order (${list}): ${meaning}`);
1393
- for (const item of output.items ?? []) {
1394
- if (!item.found) {
1395
- lines.push(`${item.identifier}: not found (${item.error ?? "unknown error"})`);
1396
- continue;
1397
- }
1398
- lines.push(`${item.identifier}: itemId=${item.itemId}; raw path ${item.itemPath}.`);
1399
- if (item.translationPaths?.length) lines.push(`Translation paths: ${item.translationPaths.join(", ")}.`);
1400
- if (item.rawItemTruncated) lines.push(`Raw item truncated preview:\n${item.rawItemPreview ?? ""}`);
1401
- else lines.push(`Raw item: ${JSON.stringify(item.rawItem)}`);
1402
- if (item.translationsTruncated) lines.push(`Translations truncated preview:\n${item.translationsPreview ?? ""}`);
1403
- else lines.push(`Translations by locale: ${JSON.stringify(item.translationsByLocale ?? {})}`);
1404
- }
1405
- for (const entry of output.values ?? []) {
1406
- if (!entry.found) {
1407
- lines.push(`${entry.path}: not found (${entry.error ?? "unknown error"})`);
1408
- continue;
1409
- }
1410
- if (entry.truncated) lines.push(`${entry.path}: truncated JSON preview:\n${entry.preview ?? ""}`);
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-search-items-tool.ts
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 summarizeForModel = (output) => {
1625
- if (!output.ok) return output.reason ?? "Survey item search is unavailable.";
1626
- const lines = [`Search "${output.query ?? ""}": ${output.returned ?? 0}/${output.totalAvailable ?? 0} matching item(s) returned.`];
1627
- for (const result of output.results ?? []) {
1628
- lines.push(`- ${result.item.fullKey} (${result.item.itemType}, itemId=${result.item.itemId}, score=${result.score})`);
1629
- for (const match of result.matches) {
1630
- const source = match.source === "translation" ? `${match.locale}.${match.contentKey}` : match.field ?? match.source;
1631
- lines.push(` match ${match.source}:${source}: ${match.text}`);
1632
- }
1633
- }
1634
- if (output.nextCursor) lines.push(`More search results are available with cursor ${output.nextCursor}.`);
1635
- if (output.ordering) lines.push(`Search-result order: ${output.ordering}`);
1636
- lines.push("Use survey_inspect with action \"item-details\" and itemRef to inspect a result.");
1637
- return lines.join("\n");
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 snapshot = getSurveyAssistantEngineContext(context?.requestContext);
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 result = unwrapToolOutput(await runTool(surveyGetCurrentContextTool, {
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
- }, context));
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 result = unwrapToolOutput(await runTool(surveySearchItemsTool, {
1949
- query: input.query,
1861
+ const query = input.query;
1862
+ const inspection = runInspectionBackend(() => searchSurveyItems({
1863
+ query,
1950
1864
  limit: input.limit,
1951
1865
  cursor: input.cursor
1952
- }, context));
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 result = unwrapToolOutput(await runTool(surveyGetItemDetailsTool, {
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
- }, context));
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: diagnostics.length === 0,
1914
+ valid: blockingCount === 0,
1988
1915
  count: diagnostics.length,
1989
- diagnostics: diagnostics.slice(0, limit),
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 broken expression references were found in the current survey draft." : `${diagnostics.length} broken expression reference${diagnostics.length === 1 ? " was" : "s were"} found. Repair every listed location, then run expression-diagnostics again before claiming completion.\n${stringifyForModel(result)}`,
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 rawResult = unwrapToolOutput(await runTool(surveyGetRawSurveyTool, {
1963
+ const rawInspection = runInspectionBackend(() => inspectRawSurvey({
2034
1964
  paths: boundedPaths,
2035
- itemIdentifiers
2036
- }, context));
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 it in a richText image block inside the create-item translation content.`);
2297
- lines.push(`New info-item translation example: { "locale": "<selected-locale>", "contentKey": "content", "content": { "type": "richText", "version": 1, "doc": { "type": "doc", "blocks": [{ "type": "image", "source": { "type": "asset", "assetId": "${importedAssetIds[0]}" }, "alt": "<meaningful description>" }] } } }.`);
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, [`asset:${operation.assetId}`]);
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$9,
2427
- TOOL_ID$8,
2428
- TOOL_ID$10,
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$9]: surveyChangeTool,
2436
- [TOOL_ID$10]: surveyAuthoringReferenceTool,
2437
- [TOOL_ID$8]: surveyExpressionTool,
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-CFh35R4d.mjs.map
2381
+ //# sourceMappingURL=tools-BvSovehT.mjs.map