@case-framework/survey-assistant 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -7
- package/dist/{attachments-B0hg3vpT.mjs → attachments-BuNni5vB.mjs} +1 -1
- package/dist/{attachments-B0hg3vpT.mjs.map → attachments-BuNni5vB.mjs.map} +1 -1
- package/dist/{authoring-references-CW38pYzK.mjs → authoring-references-DbuOKerg.mjs} +13 -4
- package/dist/authoring-references-DbuOKerg.mjs.map +1 -0
- package/dist/{capabilities-DVTfogjv.d.mts → capabilities-CmeeAjEc.d.mts} +3 -4
- package/dist/capabilities-CmeeAjEc.d.mts.map +1 -0
- package/dist/capabilities-DCMA71PP.mjs +58 -0
- package/dist/capabilities-DCMA71PP.mjs.map +1 -0
- package/dist/capabilities-default.d.mts +13 -12
- package/dist/capabilities-default.d.mts.map +1 -1
- package/dist/capabilities-default.mjs +2 -414
- package/dist/{constants-BMGTgfjv.d.mts → constants-B6HzpEsx.d.mts} +7 -28
- package/dist/constants-B6HzpEsx.d.mts.map +1 -0
- package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
- package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
- package/dist/controller-proxy-D7uQfm5f.d.mts +65 -0
- package/dist/controller-proxy-D7uQfm5f.d.mts.map +1 -0
- package/dist/{controller-proxy-DMTqer6T.mjs → controller-proxy-DEeFl9IA.mjs} +29 -26
- package/dist/controller-proxy-DEeFl9IA.mjs.map +1 -0
- package/dist/default-fhSon2RM.mjs +760 -0
- package/dist/default-fhSon2RM.mjs.map +1 -0
- package/dist/{digest-SJmoYVaK.d.mts → digest-C39WyAWG.d.mts} +2 -3
- package/dist/digest-C39WyAWG.d.mts.map +1 -0
- package/dist/digest.d.mts +1 -1
- package/dist/digest.mjs.map +1 -1
- package/dist/{engine-CQwOAcwK.mjs → engine-nXoCWYpQ.mjs} +2821 -2018
- package/dist/engine-nXoCWYpQ.mjs.map +1 -0
- package/dist/engine.d.mts +3 -3
- package/dist/engine.mjs +3 -3
- package/dist/{fair-guidance-CRYCRxQm.mjs → fair-guidance-BmO4PswW.mjs} +1 -1
- package/dist/{fair-guidance-CRYCRxQm.mjs.map → fair-guidance-BmO4PswW.mjs.map} +1 -1
- package/dist/{index-D6nqkz0O.d.mts → index-Ciq7vHIs.d.mts} +121 -197
- package/dist/index-Ciq7vHIs.d.mts.map +1 -0
- package/dist/{index-DvTqML-0.d.mts → index-D7wT6P3t.d.mts} +294 -63
- package/dist/index-D7wT6P3t.d.mts.map +1 -0
- package/dist/index-WB5kfnz0.d.mts +348 -0
- package/dist/index-WB5kfnz0.d.mts.map +1 -0
- package/dist/{index-B0lxsAzx.d.mts → index-r5riv4md.d.mts} +50 -29
- package/dist/index-r5riv4md.d.mts.map +1 -0
- package/dist/{lifecycle-BmGAIUlv.d.mts → lifecycle-Dm8u7QFh.d.mts} +40 -18
- package/dist/lifecycle-Dm8u7QFh.d.mts.map +1 -0
- package/dist/{memory-thread-repository-CjFGVNZh.mjs → memory-thread-repository-DAnOGIB3.mjs} +89 -34
- package/dist/memory-thread-repository-DAnOGIB3.mjs.map +1 -0
- package/dist/{protocol-DJUP4gc0.mjs → protocol-CF4Bh_Ch.mjs} +182 -50
- package/dist/protocol-CF4Bh_Ch.mjs.map +1 -0
- package/dist/protocol.d.mts +4 -4
- package/dist/protocol.mjs +3 -3
- package/dist/react-C_GY-Fsc.mjs +1727 -0
- package/dist/react-C_GY-Fsc.mjs.map +1 -0
- package/dist/react-integration.d.mts +2 -2
- package/dist/react-integration.mjs +2 -2
- package/dist/react.d.mts +3 -3
- package/dist/react.mjs +3 -3
- package/dist/references/assistant-operations.md +143 -68
- package/dist/references/core-rules.md +6 -3
- package/dist/references/embedded-forms.md +34 -14
- package/dist/references/expressions.md +238 -48
- package/dist/references/fair-by-design.md +9 -9
- package/dist/references/item-types.md +306 -72
- package/dist/references/localization.md +5 -4
- package/dist/references/response-variables.md +38 -0
- package/dist/references/rich-text-content.md +60 -27
- package/dist/references/source-material-surveys.md +13 -5
- package/dist/references/survey-data-model.md +32 -9
- package/dist/{request-context-BjTHNcDd.mjs → request-context-Dg4jSwN1.mjs} +4 -10
- package/dist/request-context-Dg4jSwN1.mjs.map +1 -0
- package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
- package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
- package/dist/server-agent.d.mts +53 -44
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +48 -27
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +36 -49
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +17 -12
- package/dist/server-runtime.mjs.map +1 -1
- package/dist/server-tasks.d.mts +2 -2
- package/dist/server-tasks.mjs +2 -313
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +13 -36
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +5 -5
- package/dist/server.mjs.map +1 -1
- package/dist/storage-postgres.d.mts +9 -25
- package/dist/storage-postgres.d.mts.map +1 -1
- package/dist/storage-postgres.mjs +5 -4
- package/dist/storage-postgres.mjs.map +1 -1
- package/dist/{tasks-DxzD25Zc.d.mts → tasks-B9h3LPam.d.mts} +19 -35
- package/dist/tasks-B9h3LPam.d.mts.map +1 -0
- package/dist/tasks-BZEiwgxA.mjs +396 -0
- package/dist/tasks-BZEiwgxA.mjs.map +1 -0
- package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
- package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
- package/dist/{thread-handlers-AbQbt4sX.d.mts → thread-handlers-Bef0td9N.d.mts} +10 -40
- package/dist/thread-handlers-Bef0td9N.d.mts.map +1 -0
- package/dist/{tools-CFh35R4d.mjs → tools-CEsv-WoO.mjs} +705 -578
- package/dist/tools-CEsv-WoO.mjs.map +1 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.mts +17 -55
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +759 -171
- package/dist/ui.mjs.map +1 -1
- package/docs/integration.md +51 -1
- package/package.json +49 -42
- package/dist/authoring-references-CW38pYzK.mjs.map +0 -1
- package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
- package/dist/capabilities-default.mjs.map +0 -1
- package/dist/constants-BMGTgfjv.d.mts.map +0 -1
- package/dist/controller-proxy-DMTqer6T.mjs.map +0 -1
- package/dist/controller-proxy-mRvez2qw.d.mts +0 -85
- package/dist/controller-proxy-mRvez2qw.d.mts.map +0 -1
- package/dist/digest-SJmoYVaK.d.mts.map +0 -1
- package/dist/engine-CQwOAcwK.mjs.map +0 -1
- package/dist/form-authoring-kQDgC5oz.mjs +0 -155
- package/dist/form-authoring-kQDgC5oz.mjs.map +0 -1
- package/dist/index-B0lxsAzx.d.mts.map +0 -1
- package/dist/index-BS-Xjk1h.d.mts +0 -224
- package/dist/index-BS-Xjk1h.d.mts.map +0 -1
- package/dist/index-D6nqkz0O.d.mts.map +0 -1
- package/dist/index-DvTqML-0.d.mts.map +0 -1
- package/dist/lifecycle-BmGAIUlv.d.mts.map +0 -1
- package/dist/memory-thread-repository-CjFGVNZh.mjs.map +0 -1
- package/dist/protocol-DJUP4gc0.mjs.map +0 -1
- package/dist/react-Dqlmasrs.mjs +0 -768
- package/dist/react-Dqlmasrs.mjs.map +0 -1
- package/dist/request-context-BjTHNcDd.mjs.map +0 -1
- package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
- package/dist/server-tasks.mjs.map +0 -1
- package/dist/tasks-DxzD25Zc.d.mts.map +0 -1
- package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
- package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
- package/dist/thread-handlers-AbQbt4sX.d.mts.map +0 -1
- package/dist/tools-CFh35R4d.mjs.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { d as
|
|
3
|
-
import {
|
|
4
|
-
import { r as getSurveyAssistantEngineContext, s as requireSurveyAssistantTurnState } from "./request-context-
|
|
5
|
-
import { a as surveyAuthoringReferences, i as surveyAuthoringReferenceTopics, r as getBuiltInItemTypeAuthoringReference, t as builtInSurveyAuthoringItemTypes } from "./authoring-references-
|
|
1
|
+
import { Q as SURVEY_OPERATION_KINDS, Y as assistantItemTypeManifestSchema, bt as surveyOperationSchema, f as surveyChangeSetSchema, xt as surveyOperationSchemasByKind } from "./protocol-CF4Bh_Ch.mjs";
|
|
2
|
+
import { C as isRecord$3, D as expressionMutationSchema, G as getRawSurveyItems, J as itemSummarySchema, K as itemConfigDetailsSchema, L as clampLimit, M as buildEmbeddedFormDetails, N as buildItemConfigDetails, P as buildItemTranslationEntries, Q as normalizeText, R as findDirectChildrenByParentId, T as buildExpressionMutationBatch, U as getItemIndexEntries, W as getRawSurvey, X as mapOutlineById, Y as mapItemIndexById, Z as mapRawItemsById, _t as createAssistantSurvey, ct as translationEntrySchema, d as collectExpressionMutationRecoveryTargets, et as resolveItemSummary, f as collectMalformedSurveyOperationRecoveryTargets, ft as getSurveyAssistantExpressionDiagnosticGuidance, it as summarizeItemIndexEntries, j as buildConfigSearchText, lt as unique, m as auditSurvey, nt as summarizeFocusItemNode, ot as summarizeOutlineNode, p as collectSurveyOperationRecoveryTargets, q as itemMatchesQuery, rt as summarizeItemById, st as summarizeRawItem, tt as sliceWithCursor, u as collectExpressionMutationRecoveryRefinements, ut as analyzeSurveyAssistantExpressionReferences, v as createSurveyChangeSet, vt as getAssistantVisibilityIssues, w as normalizeSurveyChangeOperations } from "./engine-nXoCWYpQ.mjs";
|
|
3
|
+
import { a as getDeterministicCreateItemTypes, o as inspectAssistantItem, s as resolveAssistantCapabilityReference } from "./capabilities-DCMA71PP.mjs";
|
|
4
|
+
import { r as getSurveyAssistantEngineContext, s as requireSurveyAssistantTurnState } from "./request-context-Dg4jSwN1.mjs";
|
|
5
|
+
import { a as surveyAuthoringReferences, i as surveyAuthoringReferenceTopics, r as getBuiltInItemTypeAuthoringReference, t as builtInSurveyAuthoringItemTypes } from "./authoring-references-DbuOKerg.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,16 +53,31 @@ const formatItemCapabilityProfile = (manifest, kind) => {
|
|
|
53
53
|
"",
|
|
54
54
|
`${manifest.displayName}: ${manifest.description ?? "No additional description."}`,
|
|
55
55
|
`Creation mode: ${manifest.create}.`,
|
|
56
|
+
`Supported operations: ${manifest.supportedOperations.join(", ")}.`,
|
|
57
|
+
manifest.authoring?.compactHint,
|
|
56
58
|
manifest.authoring?.configSummary ? `Configuration: ${manifest.authoring.configSummary}` : void 0,
|
|
57
59
|
manifest.creationPresets?.length ? `Available creation preset ids: ${manifest.creationPresets.map((preset) => `\`${preset.id}\``).join(", ")}. Use the server-trusted configuration guidance above for their semantics.` : void 0,
|
|
58
|
-
fixedSurfaces.length > 0 ? `Fixed content surfaces: ${fixedSurfaces.map((surface) => surface.key).join(", ")}.` : "Fixed content surfaces: none.",
|
|
59
|
-
patternedSurfaces.length > 0 ? `Dynamic content surfaces: ${patternedSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
|
|
60
|
-
kind === "create-item" && requiredSurfaces.length > 0 ? `Required on creation: ${requiredSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
|
|
61
|
-
manifest.referenceTopics?.length ? `Load
|
|
60
|
+
fixedSurfaces.length > 0 ? `Fixed content surfaces: ${fixedSurfaces.map((surface) => `${surface.key} (format=${surface.format})`).join(", ")}.` : "Fixed content surfaces: none.",
|
|
61
|
+
patternedSurfaces.length > 0 ? `Dynamic content surfaces: ${patternedSurfaces.map((surface) => `${surface.key} (format=${surface.format})`).join(", ")}.` : void 0,
|
|
62
|
+
(!kind || kind === "create-item") && requiredSurfaces.length > 0 ? `Required on creation: ${requiredSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
|
|
63
|
+
manifest.referenceTopics?.length ? `Load dedicated guidance with survey_authoring_reference using these exact arguments: ${manifest.referenceTopics.map((topic) => JSON.stringify({
|
|
64
|
+
topic,
|
|
65
|
+
itemType: manifest.itemType
|
|
66
|
+
})).join("; ")}.` : void 0
|
|
62
67
|
].filter((line) => Boolean(line));
|
|
63
68
|
if (kind === "create-item") lines.push("", "Creation scope: include the initial structure, configuration, and visible translations that are already known. A later successful mutation call in the same turn may target the created id; the server adds that delta to the validated turn draft.");
|
|
64
69
|
return lines.join("\n");
|
|
65
70
|
};
|
|
71
|
+
const formatItemConfigurationContract = (schema) => [
|
|
72
|
+
"## Validated item configuration",
|
|
73
|
+
"This schema validates the resulting configuration after creation or an atomic patch. patch-item-config paths are relative to this object. Inspect existing configuration and preserve surviving identities; include related translations in the same proposal.",
|
|
74
|
+
"```json",
|
|
75
|
+
JSON.stringify(z.toJSONSchema(schema, {
|
|
76
|
+
io: "input",
|
|
77
|
+
unrepresentable: "any"
|
|
78
|
+
}), null, 2),
|
|
79
|
+
"```"
|
|
80
|
+
].join("\n\n");
|
|
66
81
|
const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
67
82
|
const { $schema: _schemaVersion, ...contract } = z.toJSONSchema(surveyOperationSchemasByKind[kind], {
|
|
68
83
|
io: "input",
|
|
@@ -76,15 +91,16 @@ const formatSurveyOperationContractReference = (kind, options = {}) => {
|
|
|
76
91
|
`# \`${kind}\` operation contract`,
|
|
77
92
|
"",
|
|
78
93
|
"This JSON Schema is generated from the structural validator used by `survey_change`; it is",
|
|
79
|
-
"the canonical field shape. Cross-field and snapshot-dependent rules are
|
|
80
|
-
"proposal is validated.
|
|
81
|
-
"canonical
|
|
94
|
+
"the canonical post-normalization field shape. Cross-field and snapshot-dependent rules are",
|
|
95
|
+
"enforced when the proposal is validated. Follow focused authoring guidance when it recommends",
|
|
96
|
+
"a supported model-facing shorthand; otherwise use this canonical shape.",
|
|
82
97
|
detail === "compact" ? "This compact view preserves the operation structure while omitting deeply nested value schemas. Request detail \"full\" only for protocol debugging; load a focused rich-text, expression, form, or item-type reference for authoring guidance." : "This full view is intended for protocol debugging and may be large.",
|
|
83
98
|
"",
|
|
84
99
|
"```json",
|
|
85
100
|
JSON.stringify(renderedContract, null, 2),
|
|
86
101
|
"```",
|
|
87
|
-
...itemCapability ? ["", formatItemCapabilityProfile(itemCapability, kind)] : []
|
|
102
|
+
...itemCapability ? ["", formatItemCapabilityProfile(itemCapability, kind)] : [],
|
|
103
|
+
...options.itemConfigurationSchema && (kind === "patch-item-config" || kind === "create-item") ? [formatItemConfigurationContract(options.itemConfigurationSchema)] : []
|
|
88
104
|
].join("\n");
|
|
89
105
|
};
|
|
90
106
|
//#endregion
|
|
@@ -139,7 +155,7 @@ const suggestCanonicalNames = (input, candidates, limit = 3) => [...new Set(cand
|
|
|
139
155
|
//#endregion
|
|
140
156
|
//#region src/server/tools/tool-model-output.ts
|
|
141
157
|
const FALLBACK_MODEL_TEXT = "The tool returned no model-readable output. Review the tool arguments and retry with valid values.";
|
|
142
|
-
const unwrapToolOutput
|
|
158
|
+
const unwrapToolOutput = (value) => value && typeof value === "object" && "output" in value && value.output !== void 0 ? value.output : value;
|
|
143
159
|
const createTextModelOutput = (text) => ({
|
|
144
160
|
type: "content",
|
|
145
161
|
value: [{
|
|
@@ -148,7 +164,7 @@ const createTextModelOutput = (text) => ({
|
|
|
148
164
|
}]
|
|
149
165
|
});
|
|
150
166
|
const toTextModelOutput = (value, summarize) => {
|
|
151
|
-
const output = unwrapToolOutput
|
|
167
|
+
const output = unwrapToolOutput(value);
|
|
152
168
|
if (isValidationError(output)) return createTextModelOutput(output.message);
|
|
153
169
|
try {
|
|
154
170
|
return createTextModelOutput(summarize(output));
|
|
@@ -158,7 +174,7 @@ const toTextModelOutput = (value, summarize) => {
|
|
|
158
174
|
};
|
|
159
175
|
//#endregion
|
|
160
176
|
//#region src/server/tools/survey-authoring-reference-tool.ts
|
|
161
|
-
const TOOL_ID$
|
|
177
|
+
const TOOL_ID$6 = "survey_authoring_reference";
|
|
162
178
|
const topicSchema = z.string().min(1).max(120);
|
|
163
179
|
const inputSchema$10 = z.object({
|
|
164
180
|
topic: topicSchema.describe("The narrow authoring reference to load."),
|
|
@@ -181,7 +197,7 @@ const formatCanonicalNameRecovery = (input, candidates, noun) => {
|
|
|
181
197
|
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
198
|
};
|
|
183
199
|
const surveyAuthoringReferenceTool = createTool({
|
|
184
|
-
id: TOOL_ID$
|
|
200
|
+
id: TOOL_ID$6,
|
|
185
201
|
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
202
|
inputSchema: inputSchema$10,
|
|
187
203
|
outputSchema: outputSchema$8,
|
|
@@ -241,7 +257,8 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
241
257
|
title: itemCapability ? `${inputData.operationKind} for ${itemCapability.displayName}` : `${inputData.operationKind} operation contract`,
|
|
242
258
|
content: formatSurveyOperationContractReference(inputData.operationKind, {
|
|
243
259
|
detail: contractDetail,
|
|
244
|
-
itemCapability
|
|
260
|
+
itemCapability,
|
|
261
|
+
itemConfigurationSchema: inputData.itemType ? engineContext?.trustedCapabilities?.byItemType.get(inputData.itemType)?.configurationSchema : void 0
|
|
245
262
|
})
|
|
246
263
|
};
|
|
247
264
|
}
|
|
@@ -264,14 +281,27 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
264
281
|
title: capabilityReference.title ?? `${capability?.manifest.displayName} authoring`,
|
|
265
282
|
content: capabilityReference.markdown
|
|
266
283
|
} : void 0;
|
|
267
|
-
const
|
|
284
|
+
const focusedCapabilityProfile = inputData.topic === "item-types" && itemCapability ? {
|
|
285
|
+
title: itemCapability.displayName,
|
|
286
|
+
content: formatItemCapabilityProfile(itemCapability)
|
|
287
|
+
} : void 0;
|
|
288
|
+
const baseEntry = inputData.topic === "item-types" && inputData.itemType ? focusedBuiltInItemTypeReference ?? focusedCapabilityProfile : builtInEntry;
|
|
289
|
+
const entry = baseEntry && capabilityEntry ? {
|
|
290
|
+
title: baseEntry.title,
|
|
291
|
+
content: baseEntry.content === capabilityEntry.content ? baseEntry.content : [
|
|
292
|
+
baseEntry.content,
|
|
293
|
+
"## Item-specific constraints",
|
|
294
|
+
"Apply the following capability constraints when using the shared grammar above.",
|
|
295
|
+
capabilityEntry.content
|
|
296
|
+
].join("\n\n")
|
|
297
|
+
} : baseEntry ?? capabilityEntry;
|
|
268
298
|
if (!entry) return {
|
|
269
299
|
ok: false,
|
|
270
300
|
topic: inputData.topic,
|
|
271
301
|
itemType: inputData.itemType,
|
|
272
302
|
operationKind: inputData.operationKind,
|
|
273
303
|
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")
|
|
304
|
+
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
305
|
};
|
|
276
306
|
return {
|
|
277
307
|
ok: true,
|
|
@@ -279,35 +309,51 @@ const surveyAuthoringReferenceTool = createTool({
|
|
|
279
309
|
itemType: inputData.itemType,
|
|
280
310
|
operationKind: inputData.operationKind,
|
|
281
311
|
title: entry.title,
|
|
282
|
-
content: entry.content
|
|
312
|
+
content: inputData.topic === "item-types" && capability?.configurationSchema ? `${entry.content}\n\n${formatItemConfigurationContract(capability.configurationSchema)}` : entry.content
|
|
283
313
|
};
|
|
284
314
|
},
|
|
285
315
|
toModelOutput: (value) => toTextModelOutput(value, (output) => `# ${output.title}\n\n${output.content}`)
|
|
286
316
|
});
|
|
287
317
|
//#endregion
|
|
318
|
+
//#region src/server/tools/mutation-recovery.ts
|
|
319
|
+
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.");
|
|
320
|
+
const summarizePendingRepairs = (event) => {
|
|
321
|
+
if (!event.pendingRepairs?.length) return [];
|
|
322
|
+
const requiredIds = event.pendingRepairs.filter((repair) => repair.requiresExplicitRetry).map((repair) => repair.changeId);
|
|
323
|
+
return [
|
|
324
|
+
...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.`] : [],
|
|
325
|
+
"TURN APPLY STATE: BLOCKED. These rejected proposal obligations remain unapplied. Repair every target; successful partial retries accumulate but do not clear the remaining obligations.",
|
|
326
|
+
...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."}`)
|
|
327
|
+
];
|
|
328
|
+
};
|
|
329
|
+
//#endregion
|
|
288
330
|
//#region src/server/tools/survey-change-tool.ts
|
|
289
|
-
const TOOL_ID$
|
|
331
|
+
const TOOL_ID$5 = "survey_change";
|
|
290
332
|
const OPERATIONS_DESCRIPTION = [
|
|
291
333
|
"Typed survey operation objects to validate and package as one undoable change. Every object uses discriminator \"kind\", never \"type\".",
|
|
292
334
|
"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
335
|
"Any translations owned by an operation must be inside that same operation object. Never put translations beside the survey_change operations array.",
|
|
294
|
-
"
|
|
295
|
-
"
|
|
336
|
+
"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.",
|
|
337
|
+
"Canonical create-item shape: put parentItemId and optional zero-based index in target (omitting index appends; index: 0 inserts first); put the required 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.",
|
|
338
|
+
"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
339
|
"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
340
|
"Use remove-survey-locale with locale to destructively remove an existing survey language and all translations stored for it.",
|
|
298
341
|
"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.",
|
|
299
|
-
"
|
|
300
|
-
"
|
|
342
|
+
"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.",
|
|
343
|
+
"For an existing choice item, use add-choice-option with itemId, optional locale/index, and option: { id?, code?, label }; label is a plain string and creates its translation.",
|
|
344
|
+
"Every reorder replaces the complete inspected list: reorder-choice-options uses optionIds, reorder-form-field-groups uses groupIds, and reorder-items uses top-level parentItemId (not target.parentItemId) and itemIds for every direct child. Include every existing entry exactly once in desired order.",
|
|
301
345
|
`Use update-item-color only with a supported swatch. ${`Allowed editor item-color swatches: ${SURVEY_EDITOR_ITEM_COLORS.join(", ")}. Use one of these exact values for update-item-color; do not wait for validation feedback to choose a swatch.`}`,
|
|
302
|
-
"Use
|
|
303
|
-
"Use
|
|
346
|
+
"Use update-response-settings with naming and settings arrays for variable names, matrix coding components, option export codes and export formats. Load response-variables for the shared coding contract.",
|
|
347
|
+
"Use patch-item-config with itemId and plural patches whose JSON Pointer paths are relative to that item config root. Include related translations inside the operation. Load the focused item capability reference for its configuration and content keys.",
|
|
348
|
+
"Use update-placed-image only for an image already embedded in rich text in the live survey or validated turn draft. It can switch the placement between a local asset and an external URL and update its rendered ALT text, caption, width, height, maximum dimensions, and alignment. For a new item, prefer putting the canonical image block inside create-item.translations[].richTextBlocks. Do not use image asset metadata when the user asked to change one placed image.",
|
|
304
349
|
"When deleting referenced images and assets, include every reference-removal operation and remove-image-asset last in the same operations array.",
|
|
305
|
-
"
|
|
350
|
+
"A survey-json-patch proposal contains exactly one operation with kind survey-json-patch and one plural patches array containing every related raw edit. It must be the only operation in that survey_change call. Paths are rooted at the survey document, such as /surveyItems/<index>/config or /translations/<locale>; never start at /config or modify /$schema. Patch translation leaves rather than replacing existing locale or item-translation containers.",
|
|
306
351
|
"Keep each delta bounded; this is a per-call constraint, not a reason to end the turn. Successful chunks accumulate automatically, so substantial requests should continue with later deltas while capacity remains."
|
|
307
352
|
].join(" ");
|
|
308
353
|
const inputSchema$9 = z.object({
|
|
354
|
+
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
309
355
|
summary: z.string().min(1).max(500).describe("Short user-facing summary of the intended survey change."),
|
|
310
|
-
operations: z.array(z.unknown()).min(
|
|
356
|
+
operations: z.array(z.unknown()).min(0).max(200).describe(OPERATIONS_DESCRIPTION)
|
|
311
357
|
}).strict();
|
|
312
358
|
const rejectedOperationParseEvent = ({ issueText, summary, turnId }) => ({
|
|
313
359
|
protocolVersion: 1,
|
|
@@ -334,7 +380,6 @@ const describeOperationTarget = (operation) => {
|
|
|
334
380
|
case "update-placed-image": return `${operation.kind} ${operation.locale}:${operation.itemId}:${operation.contentKey}#${operation.imageIndex}`;
|
|
335
381
|
case "update-item-label":
|
|
336
382
|
case "update-item-color":
|
|
337
|
-
case "update-item-key":
|
|
338
383
|
case "patch-item-config":
|
|
339
384
|
case "remove-item":
|
|
340
385
|
case "move-item": return `${operation.kind} ${operation.itemId}`;
|
|
@@ -346,11 +391,12 @@ const describeOperationTarget = (operation) => {
|
|
|
346
391
|
case "move-form-field": return `${operation.kind} ${operation.target.itemId}:${operation.fieldId}`;
|
|
347
392
|
case "set-embedded-form":
|
|
348
393
|
case "remove-embedded-form": return `${operation.kind} ${operation.itemId}:${operation.optionId}`;
|
|
349
|
-
case "add-choice-option": return `${operation.kind} ${operation.itemId}:${operation.option.id ?? operation.option.
|
|
394
|
+
case "add-choice-option": return `${operation.kind} ${operation.itemId}:${operation.option.id ?? operation.option.code ?? operation.option.label}`;
|
|
350
395
|
case "update-choice-option-label":
|
|
351
396
|
case "remove-choice-option": return `${operation.kind} ${operation.itemId}:${operation.optionId}`;
|
|
352
397
|
case "reorder-choice-options": return `${operation.kind} ${operation.itemId}`;
|
|
353
398
|
case "reorder-items": return `${operation.kind} ${operation.parentItemId}`;
|
|
399
|
+
case "update-response-settings":
|
|
354
400
|
case "update-survey-metadata":
|
|
355
401
|
case "update-survey-pagination": return operation.kind;
|
|
356
402
|
case "update-survey-translation": return `${operation.kind} ${operation.locale}:${operation.surface}:${operation.contentKey}`;
|
|
@@ -359,7 +405,7 @@ const describeOperationTarget = (operation) => {
|
|
|
359
405
|
case "set-image-asset":
|
|
360
406
|
case "update-image-asset-metadata":
|
|
361
407
|
case "remove-image-asset": return `${operation.kind} ${operation.assetId}`;
|
|
362
|
-
case "create-item": return `${operation.kind} ${operation.item.id ?? operation.item.
|
|
408
|
+
case "create-item": return `${operation.kind} ${operation.item.id ?? operation.item.metadata?.itemLabel ?? operation.item.id ?? operation.item.itemType}`;
|
|
363
409
|
case "survey-json-patch": return `${operation.kind} ${operation.patches.map((patch) => patch.path).join(", ")}`;
|
|
364
410
|
}
|
|
365
411
|
};
|
|
@@ -377,11 +423,19 @@ const summarizeValidation = (event) => {
|
|
|
377
423
|
lines.push(`Draft updated (${event.operations.length} total operation${event.operations.length === 1 ? "" : "s"}). Later tools can inspect and edit the resulting draft state. The live editor is unchanged until successful turn completion.`);
|
|
378
424
|
if (event.postChange) {
|
|
379
425
|
const unavailableItemCount = event.postChange.touchedItemCount - event.postChange.availableTouchedItemCount;
|
|
380
|
-
lines.push(`Canonical post-change preview (first
|
|
426
|
+
lines.push(`Canonical post-change preview (mutation targets first, then affected parents; projected fields only): ${event.postChange.itemCount} survey item(s); locales ${event.postChange.locales.length}/${event.postChange.localeCount} shown; targets ${event.postChange.touchedTargets.length}/${event.postChange.targetCount} shown; available touched-item snapshots ${event.postChange.touchedItems.length}/${event.postChange.availableTouchedItemCount} shown${unavailableItemCount > 0 ? `; ${unavailableItemCount} touched item target(s) are absent after the change` : ""}.`);
|
|
381
427
|
lines.push(`Target preview: ${event.postChange.touchedTargets.join(", ") || "none"}. Absence from this bounded projection does not prove that a field or target is absent from the canonical draft.`);
|
|
382
428
|
for (const item of event.postChange.touchedItems) {
|
|
383
429
|
const translations = item.translations.map(({ contentKey, locale, textLength, textPreview, truncated }) => truncated ? `${locale}:${contentKey}=${JSON.stringify(`${textPreview}…`)} [truncated preview; ${textLength} characters total]` : `${locale}:${contentKey}=${JSON.stringify(textPreview)}`).join("; ");
|
|
384
|
-
lines.push(`- ${item.
|
|
430
|
+
lines.push(`- ${item.breadcrumb} (${item.itemType}, id ${item.itemId}); response slots ${item.responseSlots.length}/${item.responseSlotCount}: ${item.responseSlots.join(", ") || "none"}; translations ${item.translations.length}/${item.translationCount}: ${translations || "none"}`);
|
|
431
|
+
if (item.placement) {
|
|
432
|
+
const p = item.placement;
|
|
433
|
+
lines.push(` Stored placement: parent ${p.parentItemId}; index ${p.index} (zero-based), ${p.index + 1}/${p.siblingCount}; previous ${p.previousItemId ?? "none"}; next ${p.nextItemId ?? "none"}.${p.shuffled ? " Parent shuffles children; respondent order may differ." : ""}`);
|
|
434
|
+
}
|
|
435
|
+
if (item.childOrder) {
|
|
436
|
+
const order = item.childOrder;
|
|
437
|
+
lines.push(` Stored child order (${order.itemIds.length}/${order.total}${order.truncated ? "; truncated" : ""}): ${JSON.stringify(order.itemIds)}.${order.shuffled ? " Group shuffles children; respondent order may differ." : ""}`);
|
|
438
|
+
}
|
|
385
439
|
}
|
|
386
440
|
if (event.postChange.truncated) lines.push("The post-change preview is truncated. The validated draft still contains the complete change; inspect any relevant omitted target or field before claiming its exact post-change value.");
|
|
387
441
|
}
|
|
@@ -413,10 +467,11 @@ const summarizeValidation = (event) => {
|
|
|
413
467
|
lines.push("Warnings:");
|
|
414
468
|
for (const warning of event.validation.warnings) lines.push(`- ${warning.code}: ${warning.message}`);
|
|
415
469
|
}
|
|
470
|
+
lines.push(...summarizePendingRepairs(event));
|
|
416
471
|
return lines.join("\n");
|
|
417
472
|
};
|
|
418
473
|
const surveyChangeTool = createTool({
|
|
419
|
-
id: TOOL_ID$
|
|
474
|
+
id: TOOL_ID$5,
|
|
420
475
|
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
476
|
inputSchema: inputSchema$9,
|
|
422
477
|
outputSchema: surveyChangeSetSchema,
|
|
@@ -431,13 +486,20 @@ const surveyChangeTool = createTool({
|
|
|
431
486
|
const { turnDraft, turnId } = requireSurveyAssistantTurnState(context?.requestContext);
|
|
432
487
|
const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
|
|
433
488
|
const defaultLocale = snapshot?.ui.selectedLocale ?? snapshot?.inspection.locales[0] ?? "en";
|
|
434
|
-
const
|
|
489
|
+
const normalized = normalizeSurveyChangeOperations(inputData.operations, defaultLocale);
|
|
490
|
+
if (normalized.issues.length > 0) return turnDraft.reject(rejectedOperationParseEvent({
|
|
491
|
+
summary: inputData.summary,
|
|
492
|
+
turnId,
|
|
493
|
+
issueText: normalized.issues.slice(0, 8).map((issue) => `${issue.path}: ${issue.message}`).join("; ")
|
|
494
|
+
}), collectMalformedSurveyOperationRecoveryTargets(normalized.operations, snapshot, normalized.issues));
|
|
495
|
+
const operationsParse = z.array(surveyOperationSchema).safeParse(normalized.operations);
|
|
435
496
|
if (!operationsParse.success) return turnDraft.reject(rejectedOperationParseEvent({
|
|
436
497
|
summary: inputData.summary,
|
|
437
498
|
turnId,
|
|
438
499
|
issueText: operationsParse.error.issues.slice(0, 8).map((issue) => `${issue.path.join(".") || "operation"}: ${issue.message}`).join("; ")
|
|
439
|
-
}));
|
|
500
|
+
}), collectMalformedSurveyOperationRecoveryTargets(normalized.operations, snapshot, normalized.issues));
|
|
440
501
|
return turnDraft.stage({
|
|
502
|
+
retryOfChangeIds: inputData.retryOfChangeIds,
|
|
441
503
|
operations: operationsParse.data,
|
|
442
504
|
snapshot,
|
|
443
505
|
summary: inputData.summary
|
|
@@ -447,28 +509,39 @@ const surveyChangeTool = createTool({
|
|
|
447
509
|
});
|
|
448
510
|
//#endregion
|
|
449
511
|
//#region src/server/tools/survey-expression-tool.ts
|
|
450
|
-
const TOOL_ID$
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
reason: z.string().max(500).optional(),
|
|
454
|
-
itemIds: z.array(z.string().min(1).max(240)).max(12).optional(),
|
|
455
|
-
includeRawLocations: z.boolean().optional()
|
|
456
|
-
});
|
|
512
|
+
const TOOL_ID$4 = "survey_expression";
|
|
513
|
+
const MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL = 40;
|
|
514
|
+
const expressionMutationListSchema = z.array(expressionMutationSchema).min(1).max(MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL);
|
|
457
515
|
const prepareActionSchema = z.object({
|
|
458
516
|
action: z.literal("prepare"),
|
|
459
517
|
summary: z.string().min(1).max(500),
|
|
460
|
-
mutations:
|
|
518
|
+
mutations: expressionMutationListSchema
|
|
461
519
|
});
|
|
462
520
|
const inputSchema$8 = z.object({
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
521
|
+
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
522
|
+
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."),
|
|
523
|
+
reason: z.string().max(500).optional().describe("Context action only: optional inspection reason."),
|
|
524
|
+
itemIds: z.array(z.string().min(1).max(240)).max(12).optional().describe("Context action only: filter response slots and expression locations by exact item IDs. Omit to inspect survey-wide references, including controlling questions outside the conditioned item."),
|
|
525
|
+
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."),
|
|
526
|
+
limit: z.number().int().min(1).max(80).optional().describe("Context action only: rows per catalog page; defaults to 80."),
|
|
527
|
+
includeRawLocations: z.boolean().optional().describe("Context action only: include existing expression locations by raw item."),
|
|
528
|
+
summary: z.string().max(500).optional().describe("Prepare action only: required user-facing summary."),
|
|
529
|
+
mutations: z.array(z.unknown()).min(1).max(MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL).optional().describe(`Prepare action only: an array of 1-${MAX_EXPRESSION_MUTATIONS_PER_PROPOSAL} canonical expression mutation objects from the loaded expressions reference. Example entry: {"kind":"set-item-display-condition","itemId":"<existing-item-id>","expression":{"type":"function","functionName":"eq","arguments":[...]}}. expression is CASE expression JSON, never an expression-language snippet.`)
|
|
469
530
|
}).strict().superRefine((value, ctx) => {
|
|
470
|
-
if (value.action
|
|
471
|
-
|
|
531
|
+
if (value.action === "context") {
|
|
532
|
+
if (value.summary?.trim()) ctx.addIssue({
|
|
533
|
+
code: z.ZodIssueCode.custom,
|
|
534
|
+
path: ["summary"],
|
|
535
|
+
message: "Omit summary for context; use action=prepare to stage mutations."
|
|
536
|
+
});
|
|
537
|
+
if (value.mutations !== void 0) ctx.addIssue({
|
|
538
|
+
code: z.ZodIssueCode.custom,
|
|
539
|
+
path: ["mutations"],
|
|
540
|
+
message: "Omit mutations for context; use action=prepare to stage mutations."
|
|
541
|
+
});
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (!value.summary?.trim()) ctx.addIssue({
|
|
472
545
|
code: z.ZodIssueCode.custom,
|
|
473
546
|
path: ["summary"],
|
|
474
547
|
message: "Required for prepare."
|
|
@@ -476,7 +549,7 @@ const inputSchema$8 = z.object({
|
|
|
476
549
|
if (!value.mutations?.length) ctx.addIssue({
|
|
477
550
|
code: z.ZodIssueCode.custom,
|
|
478
551
|
path: ["mutations"],
|
|
479
|
-
message: "
|
|
552
|
+
message: "A non-empty mutation array is required for prepare."
|
|
480
553
|
});
|
|
481
554
|
});
|
|
482
555
|
const contextOutputSchema = z.object({
|
|
@@ -484,13 +557,23 @@ const contextOutputSchema = z.object({
|
|
|
484
557
|
action: z.literal("context"),
|
|
485
558
|
modelSummary: z.string(),
|
|
486
559
|
listOrdering: z.record(z.string(), z.string()).describe("Meaning of each returned list. Expression references are not positional."),
|
|
560
|
+
nextCursor: z.string().optional(),
|
|
561
|
+
totalResponseSlots: z.number().int().nonnegative().optional(),
|
|
562
|
+
surveyResponseSlotCount: z.number().int().nonnegative().optional(),
|
|
563
|
+
totalExpressionLocations: z.number().int().nonnegative().optional(),
|
|
564
|
+
unresolvedItemIds: z.array(z.string()).optional(),
|
|
487
565
|
responseSlots: z.array(z.object({
|
|
488
|
-
ref: z.
|
|
489
|
-
|
|
566
|
+
ref: z.object({
|
|
567
|
+
slotId: z.string(),
|
|
568
|
+
method: z.enum(["get", "isDefined"])
|
|
569
|
+
}),
|
|
570
|
+
valueType: z.string().optional(),
|
|
571
|
+
variableName: z.string().optional(),
|
|
572
|
+
itemId: z.string().optional()
|
|
490
573
|
})),
|
|
491
574
|
expressionLocations: z.array(z.object({
|
|
492
575
|
itemId: z.string(),
|
|
493
|
-
|
|
576
|
+
editorName: z.string(),
|
|
494
577
|
itemType: z.string(),
|
|
495
578
|
rawIndex: z.number().int().nonnegative(),
|
|
496
579
|
locations: z.array(z.string())
|
|
@@ -501,22 +584,6 @@ const createIssue = ({ severity = "error", ...input }) => ({
|
|
|
501
584
|
severity,
|
|
502
585
|
...input
|
|
503
586
|
});
|
|
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
587
|
const buildResponseSlotTypes = (snapshot) => snapshot.responseSlotTypes;
|
|
521
588
|
const rejectedEvent = ({ issues, summary, turnId, warnings = [] }) => ({
|
|
522
589
|
protocolVersion: 1,
|
|
@@ -545,18 +612,37 @@ const summarizeDraftEvent = (event) => {
|
|
|
545
612
|
lines.push("Warnings:");
|
|
546
613
|
for (const warning of event.validation.warnings) lines.push(`- ${warning.code}: ${warning.message}`);
|
|
547
614
|
}
|
|
615
|
+
lines.push(...summarizePendingRepairs(event));
|
|
548
616
|
return lines.join("\n");
|
|
549
617
|
};
|
|
550
618
|
const buildContextOutput = (snapshot, input) => {
|
|
551
619
|
const rawItems = getRawSurveyItems(snapshot);
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
620
|
+
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
621
|
+
const resolvedItems = input.itemIds?.map((identifier) => ({
|
|
622
|
+
identifier,
|
|
623
|
+
itemId: snapshot.inspection.itemIndex.find((entry) => entry.itemId === identifier || entry.editorName === identifier || entry.breadcrumb === identifier)?.itemId
|
|
556
624
|
}));
|
|
557
|
-
const itemFilter =
|
|
558
|
-
const
|
|
559
|
-
|
|
625
|
+
const itemFilter = resolvedItems?.length ? new Set(resolvedItems.flatMap((entry) => entry.itemId ? [entry.itemId] : [])) : void 0;
|
|
626
|
+
const unresolvedItemIds = resolvedItems?.filter((entry) => !entry.itemId).map((entry) => entry.identifier) ?? [];
|
|
627
|
+
const surveyResponseSlots = Object.entries(buildResponseSlotTypes(snapshot));
|
|
628
|
+
const allResponseSlots = surveyResponseSlots.filter(([ref]) => !itemFilter || snapshot.responseVariables.some((slot) => itemFilter.has(slot.itemId) && JSON.parse(ref)[0] === slot.slotId)).map(([key, valueType]) => {
|
|
629
|
+
const [slotId, method] = JSON.parse(key);
|
|
630
|
+
const slot = snapshot.responseVariables.find((slot) => slot.slotId === slotId);
|
|
631
|
+
return {
|
|
632
|
+
ref: {
|
|
633
|
+
slotId,
|
|
634
|
+
method
|
|
635
|
+
},
|
|
636
|
+
valueType,
|
|
637
|
+
variableName: slot?.variableName,
|
|
638
|
+
itemId: slot?.itemId
|
|
639
|
+
};
|
|
640
|
+
});
|
|
641
|
+
const offset = Number(input.cursor ?? 0);
|
|
642
|
+
const limit = input.limit ?? 80;
|
|
643
|
+
const responseSlots = allResponseSlots.slice(offset, offset + limit);
|
|
644
|
+
const allExpressionLocations = input.action === "context" && input.includeRawLocations === true ? rawItems.map((item, rawIndex) => {
|
|
645
|
+
if (itemFilter && !itemFilter.has(item.id)) return;
|
|
560
646
|
const locations = [
|
|
561
647
|
item.displayConditions?.root ? "displayConditions.root" : void 0,
|
|
562
648
|
...Object.keys(item.displayConditions?.components ?? {}).map((componentId) => `displayConditions.components.${componentId}`),
|
|
@@ -566,12 +652,15 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
566
652
|
].filter((location) => Boolean(location));
|
|
567
653
|
return {
|
|
568
654
|
itemId: item.id,
|
|
569
|
-
|
|
655
|
+
editorName: names.get(item.id) ?? item.id,
|
|
570
656
|
itemType: item.itemType,
|
|
571
657
|
rawIndex,
|
|
572
658
|
locations
|
|
573
659
|
};
|
|
574
660
|
}).filter((entry) => Boolean(entry)) : void 0;
|
|
661
|
+
const expressionLocations = allExpressionLocations?.slice(offset, offset + limit);
|
|
662
|
+
const totalAvailable = Math.max(allResponseSlots.length, allExpressionLocations?.length ?? 0);
|
|
663
|
+
const nextCursor = offset + limit < totalAvailable ? String(offset + limit) : void 0;
|
|
575
664
|
return {
|
|
576
665
|
ok: true,
|
|
577
666
|
action: "context",
|
|
@@ -580,13 +669,23 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
580
669
|
"Use prepare mutations for display conditions, disabled conditions, validations, prefills, and template values.",
|
|
581
670
|
"Display, disabled, validation, and prefill.when expressions must return boolean.",
|
|
582
671
|
"Use response refs exactly as listed and check their value types before choosing functions.",
|
|
672
|
+
"An isDefined ref means only that an active answer exists; it never means a particular answer such as Yes, selected, eligible, or has children.",
|
|
673
|
+
"When the condition names or implies a choice, inspect the source choice item for the exact option id, then compare its get ref with that reference using eq or list_contains.",
|
|
674
|
+
"Visibility must be acyclic, including item, ancestor, component and computed-response dependencies. Prefills do not permit cycles. Use an independent controlling answer or serialized context value. Hidden answers are retained but inactive: get is undefined and isDefined is false. Undefined/nonboolean visibility hides its target; customExpression callbacks are not supported in visibility.",
|
|
583
675
|
"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
676
|
"Response-slot order: set-like deterministic snapshot construction order; it has no survey or respondent-visible meaning.",
|
|
585
|
-
`Response
|
|
586
|
-
responseSlots.length
|
|
677
|
+
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).`,
|
|
678
|
+
`Response slots (${responseSlots.length}/${allResponseSlots.length}, offset ${offset}): ${responseSlots.map((slot) => `${JSON.stringify(slot.ref)}${slot.valueType ? `:${slot.valueType}` : ""}`).join(", ")}`,
|
|
679
|
+
nextCursor ? `More expression context is available. Call survey_expression action=context with cursor="${nextCursor}" and the same itemIds, includeRawLocations, and limit. Restart without a cursor after a draft mutation.` : "Expression context pagination complete for this scope.",
|
|
680
|
+
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
681
|
"Functions: and, or, not, eq, gt, gte, lt, lte, in_range, list_contains, sum, min, max, str_eq, date_eq.",
|
|
588
|
-
expressionLocations ? `Existing expression locations: ${expressionLocations.filter((entry) => entry.locations.length > 0).map((entry) => `${entry.
|
|
682
|
+
expressionLocations ? `Existing expression locations: ${expressionLocations.filter((entry) => entry.locations.length > 0).map((entry) => `${entry.editorName}(${entry.itemId}) -> ${entry.locations.join("|")}`).join("; ") || "none"}` : void 0
|
|
589
683
|
].filter((line) => Boolean(line)).join("\n"),
|
|
684
|
+
totalResponseSlots: allResponseSlots.length,
|
|
685
|
+
surveyResponseSlotCount: surveyResponseSlots.length,
|
|
686
|
+
...allExpressionLocations ? { totalExpressionLocations: allExpressionLocations.length } : {},
|
|
687
|
+
...nextCursor ? { nextCursor } : {},
|
|
688
|
+
...unresolvedItemIds.length ? { unresolvedItemIds } : {},
|
|
590
689
|
listOrdering: {
|
|
591
690
|
responseSlots: "Set-like deterministic snapshot construction order; position has no survey or respondent-visible meaning.",
|
|
592
691
|
expressionLocations: "Stored surveyItems array order; locations within an item are grouped by expression surface, not ranked or respondent-visible order.",
|
|
@@ -597,7 +696,7 @@ const buildContextOutput = (snapshot, input) => {
|
|
|
597
696
|
};
|
|
598
697
|
};
|
|
599
698
|
const surveyExpressionTool = createTool({
|
|
600
|
-
id: TOOL_ID$
|
|
699
|
+
id: TOOL_ID$4,
|
|
601
700
|
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
701
|
inputSchema: inputSchema$8,
|
|
603
702
|
outputSchema: outputSchema$7,
|
|
@@ -630,13 +729,22 @@ const surveyExpressionTool = createTool({
|
|
|
630
729
|
})]
|
|
631
730
|
}));
|
|
632
731
|
}
|
|
633
|
-
if (input.action === "context")
|
|
634
|
-
const contextParse = contextActionSchema.safeParse(inputData);
|
|
635
|
-
return buildContextOutput(snapshot, contextParse.success ? contextParse.data : input);
|
|
636
|
-
}
|
|
732
|
+
if (input.action === "context") return buildContextOutput(snapshot, input);
|
|
637
733
|
const { turnDraft, turnId } = requireSurveyAssistantTurnState(requestContext);
|
|
638
|
-
const reject = (event, addressedTargets = []) => turnDraft.reject(event, addressedTargets);
|
|
639
|
-
|
|
734
|
+
const reject = (event, addressedTargets = [], refinements = []) => turnDraft.reject(event, addressedTargets, refinements);
|
|
735
|
+
if (!Array.isArray(input.mutations)) return reject(rejectedEvent({
|
|
736
|
+
summary: input.summary ?? "Expression change",
|
|
737
|
+
turnId,
|
|
738
|
+
issues: [createIssue({
|
|
739
|
+
code: "invalid-expression-mutation-shape",
|
|
740
|
+
message: "mutations is required for prepare and must be a canonical mutation array."
|
|
741
|
+
})]
|
|
742
|
+
}));
|
|
743
|
+
const prepareParse = prepareActionSchema.safeParse({
|
|
744
|
+
action: input.action,
|
|
745
|
+
summary: input.summary,
|
|
746
|
+
mutations: input.mutations
|
|
747
|
+
});
|
|
640
748
|
if (!prepareParse.success) return reject(rejectedEvent({
|
|
641
749
|
summary: input.summary ?? "Expression change",
|
|
642
750
|
turnId,
|
|
@@ -644,22 +752,10 @@ const surveyExpressionTool = createTool({
|
|
|
644
752
|
code: "invalid-expression-mutation-shape",
|
|
645
753
|
message: prepareParse.error.issues.slice(0, 8).map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ")
|
|
646
754
|
})]
|
|
647
|
-
}));
|
|
755
|
+
}), collectExpressionMutationRecoveryTargets(input.mutations, snapshot));
|
|
648
756
|
const prepareInput = prepareParse.data;
|
|
649
|
-
const addressedTargets =
|
|
650
|
-
const issues =
|
|
651
|
-
const patches = [];
|
|
652
|
-
const responseSlotTypes = buildResponseSlotTypes(snapshot);
|
|
653
|
-
prepareInput.mutations.forEach((mutation, operationIndex) => {
|
|
654
|
-
buildExpressionMutationPatches({
|
|
655
|
-
mutation,
|
|
656
|
-
operationIndex,
|
|
657
|
-
issues,
|
|
658
|
-
patches,
|
|
659
|
-
responseSlotTypes,
|
|
660
|
-
snapshot
|
|
661
|
-
});
|
|
662
|
-
});
|
|
757
|
+
const addressedTargets = collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot);
|
|
758
|
+
const { issues, patches } = buildExpressionMutationBatch(prepareInput.mutations, snapshot);
|
|
663
759
|
const errors = issues.filter((issue) => issue.severity === "error");
|
|
664
760
|
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
665
761
|
if (errors.length > 0) return reject(rejectedEvent({
|
|
@@ -667,21 +763,26 @@ const surveyExpressionTool = createTool({
|
|
|
667
763
|
turnId,
|
|
668
764
|
issues: errors,
|
|
669
765
|
warnings
|
|
670
|
-
}),
|
|
671
|
-
if (patches.length === 0)
|
|
672
|
-
|
|
673
|
-
turnId,
|
|
674
|
-
issues: [createIssue({
|
|
766
|
+
}), collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot, errors), collectExpressionMutationRecoveryRefinements(prepareInput.mutations, snapshot, errors));
|
|
767
|
+
if (patches.length === 0) {
|
|
768
|
+
const noPatchIssues = prepareInput.mutations.map((_, operationIndex) => createIssue({
|
|
675
769
|
code: "no-expression-patches",
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
770
|
+
operationIndex,
|
|
771
|
+
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: []."
|
|
772
|
+
}));
|
|
773
|
+
return reject(rejectedEvent({
|
|
774
|
+
summary: prepareInput.summary,
|
|
775
|
+
turnId,
|
|
776
|
+
issues: noPatchIssues,
|
|
777
|
+
warnings
|
|
778
|
+
}), collectExpressionMutationRecoveryTargets(prepareInput.mutations, snapshot, noPatchIssues), collectExpressionMutationRecoveryRefinements(prepareInput.mutations, snapshot, noPatchIssues));
|
|
779
|
+
}
|
|
680
780
|
const operation = {
|
|
681
781
|
kind: "survey-json-patch",
|
|
682
782
|
patches
|
|
683
783
|
};
|
|
684
784
|
const event = turnDraft.stage({
|
|
785
|
+
retryOfChangeIds: input.retryOfChangeIds,
|
|
685
786
|
addressedTargets,
|
|
686
787
|
allowExpressionJsonPatches: true,
|
|
687
788
|
handledMutationRoutes: ["expression"],
|
|
@@ -700,8 +801,77 @@ const surveyExpressionTool = createTool({
|
|
|
700
801
|
toModelOutput: (value) => toTextModelOutput(value, (output) => "action" in output && output.action === "context" ? output.modelSummary : summarizeDraftEvent(output))
|
|
701
802
|
});
|
|
702
803
|
//#endregion
|
|
703
|
-
//#region src/server/tools/
|
|
704
|
-
const
|
|
804
|
+
//#region src/server/tools/parse-inspection-output.ts
|
|
805
|
+
const parseInspectionOutput = (schema, value, backend) => {
|
|
806
|
+
const parsed = schema.safeParse(value);
|
|
807
|
+
if (!parsed.success) throw new Error(`${backend} produced invalid inspection output.`, { cause: parsed.error });
|
|
808
|
+
return parsed.data;
|
|
809
|
+
};
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/server/tools/response-variable-summary.ts
|
|
812
|
+
const MAX_COMPONENTS = 50;
|
|
813
|
+
const responseVariableSummarySchema = z.object({
|
|
814
|
+
slotId: z.string(),
|
|
815
|
+
itemId: z.string(),
|
|
816
|
+
variableName: z.string(),
|
|
817
|
+
valueType: z.string(),
|
|
818
|
+
primitiveId: z.string().optional(),
|
|
819
|
+
rowId: z.string().optional(),
|
|
820
|
+
columnId: z.string().optional(),
|
|
821
|
+
namingKind: z.enum(["variable", "matrix"]).optional(),
|
|
822
|
+
computed: z.object({
|
|
823
|
+
operation: z.string(),
|
|
824
|
+
sourceSlotIds: z.array(z.string())
|
|
825
|
+
}).optional(),
|
|
826
|
+
computedSourcesTruncated: z.boolean(),
|
|
827
|
+
referenceValueKeys: z.record(z.string(), z.string()).optional(),
|
|
828
|
+
referenceValueScores: z.record(z.string(), z.number()).optional(),
|
|
829
|
+
optionIds: z.array(z.string()),
|
|
830
|
+
optionsTotal: z.number(),
|
|
831
|
+
optionsTruncated: z.boolean()
|
|
832
|
+
});
|
|
833
|
+
const responseOptionIds = (slot) => [.../* @__PURE__ */ new Set([
|
|
834
|
+
...Object.keys(slot.referenceValueKeys ?? {}),
|
|
835
|
+
...Object.keys(slot.referenceValueScores ?? {}),
|
|
836
|
+
...(slot.allowedValues ?? []).flatMap((value) => value.type === "reference" ? [value.value] : value.type === "reference[]" ? value.value : [])
|
|
837
|
+
])];
|
|
838
|
+
/** Coding discovery excludes config paths and duplicated translations for every survey locale. */
|
|
839
|
+
const summarizeResponseVariable = (slot) => {
|
|
840
|
+
const optionIds = responseOptionIds(slot);
|
|
841
|
+
const take = (values) => values && Object.fromEntries(optionIds.slice(0, MAX_COMPONENTS).filter((id) => Object.hasOwn(values, id)).map((id) => [id, values[id]]));
|
|
842
|
+
return {
|
|
843
|
+
slotId: slot.slotId,
|
|
844
|
+
itemId: slot.itemId,
|
|
845
|
+
variableName: slot.variableName,
|
|
846
|
+
valueType: slot.valueType,
|
|
847
|
+
primitiveId: slot.primitiveId,
|
|
848
|
+
rowId: slot.rowId,
|
|
849
|
+
columnId: slot.columnId,
|
|
850
|
+
namingKind: slot.naming?.kind,
|
|
851
|
+
computed: slot.computed && {
|
|
852
|
+
...slot.computed,
|
|
853
|
+
sourceSlotIds: slot.computed.sourceSlotIds.slice(0, MAX_COMPONENTS)
|
|
854
|
+
},
|
|
855
|
+
computedSourcesTruncated: (slot.computed?.sourceSlotIds.length ?? 0) > MAX_COMPONENTS,
|
|
856
|
+
referenceValueKeys: take(slot.referenceValueKeys),
|
|
857
|
+
referenceValueScores: take(slot.referenceValueScores),
|
|
858
|
+
optionIds: optionIds.slice(0, MAX_COMPONENTS),
|
|
859
|
+
optionsTotal: optionIds.length,
|
|
860
|
+
optionsTruncated: optionIds.length > MAX_COMPONENTS
|
|
861
|
+
};
|
|
862
|
+
};
|
|
863
|
+
const responseVariableMatchesQuery = (slot, query) => [
|
|
864
|
+
slot.slotId,
|
|
865
|
+
slot.itemId,
|
|
866
|
+
slot.variableName,
|
|
867
|
+
slot.primitiveId,
|
|
868
|
+
slot.rowId,
|
|
869
|
+
slot.columnId,
|
|
870
|
+
...responseOptionIds(slot),
|
|
871
|
+
...Object.values(slot.referenceValueKeys ?? {})
|
|
872
|
+
].some((value) => value?.toLowerCase().includes(query));
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/server/tools/inspect-current-context.ts
|
|
705
875
|
const contextScopeSchema = z.enum([
|
|
706
876
|
"summary",
|
|
707
877
|
"selected-item",
|
|
@@ -723,9 +893,9 @@ const translationSnippetSchema = z.object({
|
|
|
723
893
|
const inputSchema$7 = z.object({
|
|
724
894
|
scopes: z.array(contextScopeSchema).min(1).default(["summary"]).describe("Which current editor context sections to inspect."),
|
|
725
895
|
itemId: z.string().optional().describe("Optional item id to inspect. Defaults to the selected/focused item."),
|
|
726
|
-
query: z.string().max(
|
|
896
|
+
query: z.string().max(180).optional().describe("Optional text used to find matching item IDs, editor names, labels, or response variables."),
|
|
727
897
|
cursor: z.string().optional().describe("Optional numeric cursor returned by a previous outline or response slot request."),
|
|
728
|
-
outlineMode: outlineModeSchema$1.default("full").optional().describe("For outline scope: \"full\" pages the complete survey
|
|
898
|
+
outlineMode: outlineModeSchema$1.default("full").optional().describe("For outline scope: \"full\" pages the complete survey item index, \"focused\" returns the selected/focused neighborhood, and \"children\" returns direct children of itemId."),
|
|
729
899
|
limit: z.number().int().min(1).max(50).default(12).describe("Maximum number of rows to return for list-like scopes.")
|
|
730
900
|
});
|
|
731
901
|
const outputSchema$6 = z.object({
|
|
@@ -739,6 +909,7 @@ const outputSchema$6 = z.object({
|
|
|
739
909
|
sessionId: z.string().nullable(),
|
|
740
910
|
view: z.string(),
|
|
741
911
|
selectedItemId: z.string().optional(),
|
|
912
|
+
responseSlotId: z.string().optional(),
|
|
742
913
|
focusItemId: z.string().optional(),
|
|
743
914
|
selectedLocale: z.string(),
|
|
744
915
|
recentItemIds: z.array(z.string()),
|
|
@@ -788,7 +959,7 @@ const outputSchema$6 = z.object({
|
|
|
788
959
|
nextCursor: z.string().optional(),
|
|
789
960
|
truncated: z.boolean(),
|
|
790
961
|
ordering: z.string(),
|
|
791
|
-
slots: z.array(
|
|
962
|
+
slots: z.array(responseVariableSummarySchema),
|
|
792
963
|
note: z.string().optional()
|
|
793
964
|
}).optional(),
|
|
794
965
|
uiContext: z.object({
|
|
@@ -842,13 +1013,14 @@ const flattenFocusDescendants = (node) => {
|
|
|
842
1013
|
};
|
|
843
1014
|
const getCandidateItems = (snapshot) => {
|
|
844
1015
|
const outlineById = mapOutlineById(snapshot);
|
|
845
|
-
return
|
|
1016
|
+
return summarizeItemIndexEntries(getItemIndexEntries(snapshot), outlineById);
|
|
846
1017
|
};
|
|
847
1018
|
const buildSummary = (snapshot) => ({
|
|
848
1019
|
rootItem: snapshot.inspection.outline.filter((item) => item.depth === 0).map(summarizeOutlineNode)[0],
|
|
849
1020
|
sessionId: snapshot.version.sessionId,
|
|
850
1021
|
view: snapshot.ui.view,
|
|
851
1022
|
selectedItemId: snapshot.ui.selectedItemId,
|
|
1023
|
+
responseSlotId: snapshot.ui.responseSlotId,
|
|
852
1024
|
focusItemId: snapshot.ui.focusItemId,
|
|
853
1025
|
selectedLocale: snapshot.ui.selectedLocale,
|
|
854
1026
|
recentItemIds: snapshot.ui.recentItemIds ?? [],
|
|
@@ -856,7 +1028,7 @@ const buildSummary = (snapshot) => ({
|
|
|
856
1028
|
locales: snapshot.inspection.locales,
|
|
857
1029
|
itemCount: snapshot.inspection.itemCount,
|
|
858
1030
|
historyIndex: snapshot.version.historyIndex,
|
|
859
|
-
outlineItemCount: snapshot.inspection.
|
|
1031
|
+
outlineItemCount: snapshot.inspection.itemIndex.length,
|
|
860
1032
|
responseSlotCount: snapshot.inspection.responseSlots.length,
|
|
861
1033
|
availableItemTypes: snapshot.capabilities.itemTypes.map((entry) => entry.itemType),
|
|
862
1034
|
deterministicCreateItemTypes: getDeterministicCreateItemTypes(snapshot.capabilities),
|
|
@@ -864,7 +1036,7 @@ const buildSummary = (snapshot) => ({
|
|
|
864
1036
|
});
|
|
865
1037
|
const buildSelectedItem = (snapshot, input) => {
|
|
866
1038
|
const outlineById = mapOutlineById(snapshot);
|
|
867
|
-
const indexById =
|
|
1039
|
+
const indexById = mapItemIndexById(snapshot);
|
|
868
1040
|
const resolvedInputItem = input.itemId ? resolveItemSummary(input.itemId, indexById, outlineById) : void 0;
|
|
869
1041
|
const requestedItemId = resolvedInputItem?.itemId ?? input.itemId ?? snapshot.ui.selectedItemId ?? snapshot.ui.focusItemId;
|
|
870
1042
|
if (!requestedItemId) return {
|
|
@@ -897,11 +1069,12 @@ const buildSelectedItem = (snapshot, input) => {
|
|
|
897
1069
|
const buildOutline = (snapshot, input) => {
|
|
898
1070
|
const query = normalizeText(input.query);
|
|
899
1071
|
const outlineById = mapOutlineById(snapshot);
|
|
900
|
-
const indexById =
|
|
1072
|
+
const indexById = mapItemIndexById(snapshot);
|
|
901
1073
|
const resolvedInputItem = input.itemId ? resolveItemSummary(input.itemId, indexById, outlineById) : void 0;
|
|
902
1074
|
const requestedItemId = resolvedInputItem?.itemId ?? input.itemId ?? snapshot.ui.focusItemId ?? snapshot.ui.selectedItemId;
|
|
903
1075
|
const outlineMode = input.outlineMode ?? "full";
|
|
904
1076
|
let strategy = "focused-snapshot-outline";
|
|
1077
|
+
let parentShuffled;
|
|
905
1078
|
let items;
|
|
906
1079
|
let note;
|
|
907
1080
|
if (query) {
|
|
@@ -910,8 +1083,12 @@ const buildOutline = (snapshot, input) => {
|
|
|
910
1083
|
} else if (outlineMode === "children" && requestedItemId) {
|
|
911
1084
|
strategy = "direct-children";
|
|
912
1085
|
const parent = resolvedInputItem ?? summarizeItemById(requestedItemId, indexById, outlineById);
|
|
913
|
-
items = parent ?
|
|
914
|
-
if (!parent) note = "The requested item could not be resolved by item
|
|
1086
|
+
items = parent ? findDirectChildrenByParentId(getItemIndexEntries(snapshot), outlineById, parent.itemId) : [];
|
|
1087
|
+
if (!parent) note = "The requested item could not be resolved by exact item ID.";
|
|
1088
|
+
else {
|
|
1089
|
+
const rawParent = snapshot.survey.surveyItems.find((item) => item.id === parent.itemId);
|
|
1090
|
+
if (rawParent?.itemType === "group") parentShuffled = isRecord$3(rawParent.config) && rawParent.config.shuffleItems === true;
|
|
1091
|
+
}
|
|
915
1092
|
} else if (outlineMode === "focused" && requestedItemId && snapshot.inspection.focus?.focusItemId === requestedItemId) {
|
|
916
1093
|
const focus = snapshot.inspection.focus;
|
|
917
1094
|
const descendantIds = snapshot.inspection.focusItem ? flattenFocusDescendants(snapshot.inspection.focusItem).map((item) => item.itemId) : [];
|
|
@@ -928,7 +1105,7 @@ const buildOutline = (snapshot, input) => {
|
|
|
928
1105
|
items = item ? [item] : [];
|
|
929
1106
|
note = "The requested item is outside the selected focus tree in this snapshot.";
|
|
930
1107
|
} else {
|
|
931
|
-
strategy = "full-
|
|
1108
|
+
strategy = "full-item-index";
|
|
932
1109
|
items = getCandidateItems(snapshot);
|
|
933
1110
|
}
|
|
934
1111
|
const page = sliceWithCursor(items, input);
|
|
@@ -941,15 +1118,21 @@ const buildOutline = (snapshot, input) => {
|
|
|
941
1118
|
returned: page.items.length,
|
|
942
1119
|
nextCursor: page.nextCursor,
|
|
943
1120
|
truncated: page.truncated,
|
|
944
|
-
ordering:
|
|
1121
|
+
ordering: {
|
|
1122
|
+
"full-item-index": "Survey-tree preorder: configured group-child order is preserved; unparented items are appended by item id. Shuffled groups may render a different respondent order.",
|
|
1123
|
+
"direct-children": parentShuffled === void 0 ? "No resolved parent group; this result does not establish sibling order." : parentShuffled ? "Stored group-child order; this group shuffles children, so respondent order may differ." : "Stored group-child order; this group does not shuffle children.",
|
|
1124
|
+
"query-match": "Survey-tree preorder after filtering by the query; this is not relevance-ranked.",
|
|
1125
|
+
"item-match": "Single matched item; this does not establish sibling order.",
|
|
1126
|
+
"focused-snapshot-outline": "Relationship grouping for the focused item, not a single positional survey order."
|
|
1127
|
+
}[strategy],
|
|
945
1128
|
items: page.items,
|
|
946
1129
|
note
|
|
947
1130
|
};
|
|
948
1131
|
};
|
|
949
1132
|
const buildResponseSlots = (snapshot, input) => {
|
|
950
|
-
const responseSlots = snapshot.
|
|
1133
|
+
const responseSlots = snapshot.responseVariables;
|
|
951
1134
|
const query = normalizeText(input.query);
|
|
952
|
-
const filtered = query ? responseSlots.filter((slot) => slot
|
|
1135
|
+
const filtered = query ? responseSlots.filter((slot) => responseVariableMatchesQuery(slot, query)) : responseSlots;
|
|
953
1136
|
const page = sliceWithCursor(filtered, input);
|
|
954
1137
|
return {
|
|
955
1138
|
available: true,
|
|
@@ -959,131 +1142,72 @@ const buildResponseSlots = (snapshot, input) => {
|
|
|
959
1142
|
nextCursor: page.nextCursor,
|
|
960
1143
|
truncated: page.truncated,
|
|
961
1144
|
ordering: "Set-like response references in deterministic snapshot construction order; do not interpret their position as survey or respondent order.",
|
|
962
|
-
slots: page.items
|
|
1145
|
+
slots: page.items.map(summarizeResponseVariable)
|
|
963
1146
|
};
|
|
964
1147
|
};
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
if (
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1148
|
+
const inspectSurveyCurrentContext = (inputData, requestContext) => {
|
|
1149
|
+
const input = inputSchema$7.parse({
|
|
1150
|
+
...inputData,
|
|
1151
|
+
outlineMode: inputData.outlineMode ?? "full"
|
|
1152
|
+
});
|
|
1153
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1154
|
+
if (!snapshot) return {
|
|
1155
|
+
ok: false,
|
|
1156
|
+
reason: "No live editor snapshot was provided with this assistant turn.",
|
|
1157
|
+
scopes: input.scopes
|
|
1158
|
+
};
|
|
1159
|
+
const scopes = unique(input.scopes);
|
|
1160
|
+
const listOrdering = {};
|
|
1161
|
+
if (scopes.includes("summary")) Object.assign(listOrdering, {
|
|
1162
|
+
"summary.recentItemIds": "Most-recent-first UI history; not survey structure.",
|
|
1163
|
+
"summary.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1164
|
+
"summary.availableItemTypes": "Capability registry order; treat as a set of available types.",
|
|
1165
|
+
"summary.deterministicCreateItemTypes": "Capability registry order; treat as a set of available types.",
|
|
1166
|
+
"summary.itemTypeDetails": "Capability registry order; treat as a set of available type definitions."
|
|
1167
|
+
});
|
|
1168
|
+
if (scopes.includes("selected-item")) Object.assign(listOrdering, {
|
|
1169
|
+
"selectedItem.pathItems": "Root-to-selected survey-tree path.",
|
|
1170
|
+
"selectedItem.siblingItems": "Source survey item storage order; not necessarily respondent-visible order.",
|
|
1171
|
+
"selectedItem.childItems": "Stored group-child order when the focused item is a group.",
|
|
1172
|
+
"selectedItem.translationSnippets": "Grouped by locale and then source translation-key order; not positional survey content.",
|
|
1173
|
+
"selectedItem.descendantItems": "Depth-first survey-tree traversal from the selected item."
|
|
1174
|
+
});
|
|
1175
|
+
if (scopes.includes("ui-context")) Object.assign(listOrdering, {
|
|
1176
|
+
"uiContext.openPanels": "UI-provided set-like list; position has no meaning.",
|
|
1177
|
+
"uiContext.highlights": "UI-provided set-like list; position has no meaning."
|
|
1178
|
+
});
|
|
1179
|
+
if (scopes.includes("history")) listOrdering["history.recent"] = "Chronological editor history, oldest to newest within this returned window.";
|
|
1180
|
+
const output = {
|
|
1181
|
+
ok: true,
|
|
1182
|
+
revision: snapshot.version.revision,
|
|
1183
|
+
contentHash: snapshot.version.contentHash,
|
|
1184
|
+
scopes,
|
|
1185
|
+
...Object.keys(listOrdering).length > 0 ? { listOrdering } : {}
|
|
1186
|
+
};
|
|
1187
|
+
if (scopes.includes("summary")) output.summary = buildSummary(snapshot);
|
|
1188
|
+
if (scopes.includes("selected-item")) output.selectedItem = buildSelectedItem(snapshot, input);
|
|
1189
|
+
if (scopes.includes("outline")) output.outline = buildOutline(snapshot, input);
|
|
1190
|
+
if (scopes.includes("response-slots")) output.responseSlots = buildResponseSlots(snapshot, input);
|
|
1191
|
+
if (scopes.includes("ui-context")) output.uiContext = {
|
|
1192
|
+
activeSurface: snapshot.ui.activeSurface,
|
|
1193
|
+
openPanels: snapshot.ui.openPanels ?? [],
|
|
1194
|
+
highlights: snapshot.ui.highlights ?? []
|
|
1195
|
+
};
|
|
1196
|
+
if (scopes.includes("history") && snapshot.ui.history) output.history = snapshot.ui.history;
|
|
1197
|
+
return parseInspectionOutput(outputSchema$6, output, "Current-context inspection");
|
|
1005
1198
|
};
|
|
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
1199
|
//#endregion
|
|
1074
|
-
//#region src/server/tools/
|
|
1075
|
-
const TOOL_ID$6 = "survey_get_item_details";
|
|
1076
|
-
const DEFAULT_TEXT_LIMIT$1 = 400;
|
|
1077
|
-
const MAX_TEXT_LIMIT = 2e3;
|
|
1078
|
-
const DEFAULT_CHILDREN_LIMIT = 20;
|
|
1200
|
+
//#region src/server/tools/inspect-item-details.ts
|
|
1079
1201
|
const inputSchema$6 = z.object({
|
|
1080
|
-
|
|
1202
|
+
cursor: z.string().optional(),
|
|
1203
|
+
responseVariablesLimit: z.number().int().min(1).max(50).default(20),
|
|
1204
|
+
itemId: z.string().describe("Exact persistent itemId. Discover candidates by editor name, breadcrumb or content using survey_inspect search; duplicate names are allowed, so select the intended ID."),
|
|
1081
1205
|
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(
|
|
1206
|
+
textLimit: z.number().int().min(20).max(2e3).default(400).describe("Maximum characters returned per translation snippet."),
|
|
1083
1207
|
includeChildren: z.boolean().optional().describe("Deprecated alias for includeSurveyChildren. This means survey-tree children only, not choice options or form fields."),
|
|
1084
1208
|
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
1209
|
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(
|
|
1210
|
+
childrenLimit: z.number().int().min(1).max(50).default(20).describe("Maximum number of direct survey-tree children to return.")
|
|
1087
1211
|
});
|
|
1088
1212
|
const outputSchema$5 = z.object({
|
|
1089
1213
|
ok: z.boolean(),
|
|
@@ -1094,140 +1218,96 @@ const outputSchema$5 = z.object({
|
|
|
1094
1218
|
surveyChildrenTruncated: z.boolean().optional(),
|
|
1095
1219
|
listOrdering: z.record(z.string(), z.string()).optional().describe("Meaning of each returned list. A list is positional only when its entry says so."),
|
|
1096
1220
|
configDetails: itemConfigDetailsSchema.optional(),
|
|
1221
|
+
responseVariables: z.array(responseVariableSummarySchema).optional(),
|
|
1222
|
+
responseVariablesTotal: z.number().optional(),
|
|
1223
|
+
responseVariablesTruncated: z.boolean().optional(),
|
|
1224
|
+
responseVariablesNextCursor: z.string().optional(),
|
|
1225
|
+
responseVariablesNote: z.string().optional(),
|
|
1097
1226
|
capabilityDetails: z.object({
|
|
1098
1227
|
summary: z.string(),
|
|
1099
1228
|
details: z.unknown().optional()
|
|
1100
1229
|
}).optional()
|
|
1101
1230
|
});
|
|
1102
|
-
const
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1231
|
+
const inspectSurveyItemDetails = (inputData, requestContext) => {
|
|
1232
|
+
const input = inputSchema$6.parse({
|
|
1233
|
+
...inputData,
|
|
1234
|
+
includeSurveyChildren: inputData.includeSurveyChildren ?? inputData.includeChildren
|
|
1235
|
+
});
|
|
1236
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1237
|
+
if (!snapshot) return {
|
|
1238
|
+
ok: false,
|
|
1239
|
+
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1240
|
+
};
|
|
1241
|
+
const outlineById = mapOutlineById(snapshot);
|
|
1242
|
+
const indexById = mapItemIndexById(snapshot);
|
|
1243
|
+
const rawItemsById = mapRawItemsById(snapshot);
|
|
1244
|
+
const item = resolveItemSummary(input.itemId, indexById, outlineById);
|
|
1245
|
+
if (!item) return {
|
|
1246
|
+
ok: false,
|
|
1247
|
+
reason: `No item matching "${input.itemId}" (by exact itemId) was found in the current context snapshot.`
|
|
1248
|
+
};
|
|
1249
|
+
const rawItem = rawItemsById.get(item.itemId);
|
|
1250
|
+
const rawConfig = rawItem?.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1251
|
+
const output = {
|
|
1252
|
+
ok: true,
|
|
1253
|
+
item,
|
|
1254
|
+
translations: buildItemTranslationEntries(snapshot, item.itemId, input),
|
|
1255
|
+
listOrdering: {
|
|
1256
|
+
translations: "Grouped by locale and then source translation-key order; this list is not positional survey content.",
|
|
1257
|
+
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
1258
|
}
|
|
1259
|
+
};
|
|
1260
|
+
if (input.includeSurveyChildren) {
|
|
1261
|
+
const entries = getItemIndexEntries(snapshot);
|
|
1262
|
+
const allChildren = findDirectChildrenByParentId(entries, outlineById, item.itemId);
|
|
1263
|
+
const limit = clampLimit(input.childrenLimit);
|
|
1264
|
+
output.surveyChildren = allChildren.slice(0, limit);
|
|
1265
|
+
output.surveyChildrenTruncated = allChildren.length > limit;
|
|
1111
1266
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1267
|
+
if (input.includeItemConfig) {
|
|
1268
|
+
const variables = snapshot.responseVariables.filter((slot) => slot.itemId === item.itemId);
|
|
1269
|
+
const page = sliceWithCursor(variables, {
|
|
1270
|
+
cursor: input.cursor,
|
|
1271
|
+
limit: input.responseVariablesLimit
|
|
1272
|
+
});
|
|
1273
|
+
output.responseVariables = page.items.map(summarizeResponseVariable);
|
|
1274
|
+
output.responseVariablesTotal = variables.length;
|
|
1275
|
+
output.responseVariablesTruncated = page.truncated;
|
|
1276
|
+
output.responseVariablesNextCursor = page.nextCursor;
|
|
1277
|
+
output.responseVariablesNote = "Use responseVariablesNextCursor as cursor with the same itemRef to continue. Option and computed-source summaries include up to 50 entries; inspect exact raw-json config paths for complete domains and export settings. Restart pagination after mutations.";
|
|
1278
|
+
output.configDetails = rawItem ? buildItemConfigDetails(rawItem) : void 0;
|
|
1279
|
+
output.capabilityDetails = rawItem ? inspectAssistantItem({
|
|
1280
|
+
capabilities: snapshot.trustedCapabilities,
|
|
1281
|
+
item: rawItem
|
|
1282
|
+
}) : void 0;
|
|
1283
|
+
if (rawItem?.itemType === "choiceItem" && output.configDetails?.choiceOptions) {
|
|
1284
|
+
const rawChoiceConfig = rawItem.config && typeof rawItem.config === "object" && !Array.isArray(rawItem.config) ? rawItem.config : void 0;
|
|
1285
|
+
const rawOptions = Array.isArray(rawChoiceConfig?.options) ? rawChoiceConfig.options : [];
|
|
1286
|
+
for (const option of output.configDetails.choiceOptions) {
|
|
1287
|
+
const rawOption = rawOptions.find((candidate) => candidate && typeof candidate === "object" && "id" in candidate && candidate.id === option.id);
|
|
1288
|
+
const embeddedForm = rawOption && typeof rawOption === "object" && "embeddedForm" in rawOption ? rawOption.embeddedForm : void 0;
|
|
1289
|
+
const embeddedFormDetails = buildEmbeddedFormDetails(option, embeddedForm);
|
|
1290
|
+
if (embeddedFormDetails?.formFieldGroups?.length) output.configDetails.formFieldGroups = [...output.configDetails.formFieldGroups ?? [], ...embeddedFormDetails.formFieldGroups];
|
|
1291
|
+
}
|
|
1121
1292
|
}
|
|
1122
1293
|
}
|
|
1123
|
-
return
|
|
1124
|
-
};
|
|
1125
|
-
const summarizeForModel$2 = (output) => {
|
|
1126
|
-
if (!output.ok) return output.reason ?? "Item details are unavailable.";
|
|
1127
|
-
const lines = [];
|
|
1128
|
-
if (output.item) lines.push(`Item: ${output.item.fullKey} (${output.item.itemType})${output.item.itemLabel ? ` - ${output.item.itemLabel}` : ""}.`);
|
|
1129
|
-
if (output.translations?.length) lines.push(`Translations: ${output.translations.map((entry) => `${entry.locale}.${entry.contentKey}: ${entry.text}`).join("; ")}`);
|
|
1130
|
-
else lines.push("Translations: none found for this item.");
|
|
1131
|
-
if (output.surveyChildren) {
|
|
1132
|
-
lines.push(`Survey-tree children (${output.surveyChildren.length}):`);
|
|
1133
|
-
for (const child of output.surveyChildren) lines.push(`- ${child.fullKey} (${child.itemType})${child.itemLabel ? ` - ${child.itemLabel}` : ""}`);
|
|
1134
|
-
if (output.surveyChildrenTruncated) lines.push("More children exist than were returned; narrow down or raise childrenLimit.");
|
|
1135
|
-
}
|
|
1136
|
-
if (output.configDetails) {
|
|
1137
|
-
if (output.configDetails.listOrdering) for (const [list, meaning] of Object.entries(output.configDetails.listOrdering)) lines.push(`List order (${list}): ${meaning}`);
|
|
1138
|
-
lines.push(...summarizeConfigDetails(output.configDetails));
|
|
1139
|
-
}
|
|
1140
|
-
if (output.capabilityDetails) {
|
|
1141
|
-
lines.push(`Item capability: ${output.capabilityDetails.summary}`);
|
|
1142
|
-
if (output.capabilityDetails.details !== void 0) lines.push(`Capability details: ${JSON.stringify(output.capabilityDetails.details)}`);
|
|
1143
|
-
}
|
|
1144
|
-
return lines.join("\n");
|
|
1294
|
+
return parseInspectionOutput(outputSchema$5, output, "Item-details inspection");
|
|
1145
1295
|
};
|
|
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
1296
|
//#endregion
|
|
1217
|
-
//#region src/server/tools/
|
|
1218
|
-
const TOOL_ID$5 = "survey_get_raw_survey";
|
|
1297
|
+
//#region src/server/tools/inspect-raw-survey.ts
|
|
1219
1298
|
const DEFAULT_VALUE_CHARACTER_LIMIT = 12e3;
|
|
1220
1299
|
const MAX_VALUE_CHARACTER_LIMIT = 6e4;
|
|
1221
1300
|
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
1301
|
const inputSchema$5 = z.object({
|
|
1223
1302
|
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
|
-
itemIdentifiers: z.array(z.string().min(1).max(240)).min(1).max(20).optional().describe("Optional item
|
|
1303
|
+
itemIdentifiers: z.array(z.string().min(1).max(240)).min(1).max(20).optional().describe("Optional exact item IDs to inspect. Returns exact /surveyItems/<index> paths plus item translation paths for each item."),
|
|
1304
|
+
textOffset: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
1225
1305
|
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
1306
|
});
|
|
1227
1307
|
const rawSurveyItemPathSchema = z.object({
|
|
1228
1308
|
path: z.string(),
|
|
1229
1309
|
id: z.string(),
|
|
1230
|
-
|
|
1310
|
+
editorName: z.string(),
|
|
1231
1311
|
itemType: z.string(),
|
|
1232
1312
|
parentId: z.string().optional(),
|
|
1233
1313
|
childCount: z.number().int().nonnegative().optional()
|
|
@@ -1250,6 +1330,11 @@ const outputSchema$4 = z.object({
|
|
|
1250
1330
|
found: z.boolean(),
|
|
1251
1331
|
value: z.unknown().optional(),
|
|
1252
1332
|
preview: z.string().optional(),
|
|
1333
|
+
text: z.string().optional(),
|
|
1334
|
+
textOffset: z.number().int().nonnegative().optional(),
|
|
1335
|
+
textLength: z.number().int().nonnegative().optional(),
|
|
1336
|
+
nextTextOffset: z.number().int().nonnegative().optional(),
|
|
1337
|
+
recovery: z.string().optional(),
|
|
1253
1338
|
truncated: z.boolean().optional(),
|
|
1254
1339
|
error: z.string().optional()
|
|
1255
1340
|
})).optional(),
|
|
@@ -1283,6 +1368,7 @@ const buildOverview = (snapshot) => {
|
|
|
1283
1368
|
const rawSurvey = getRawSurvey(snapshot);
|
|
1284
1369
|
const rawItems = getRawSurveyItems(snapshot);
|
|
1285
1370
|
const parentByChildId = buildParentByChildId(rawSurvey);
|
|
1371
|
+
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
1286
1372
|
return {
|
|
1287
1373
|
topLevelKeys: Object.keys(rawSurvey).sort(),
|
|
1288
1374
|
surveyItemCount: rawItems.length,
|
|
@@ -1290,7 +1376,7 @@ const buildOverview = (snapshot) => {
|
|
|
1290
1376
|
itemPaths: rawItems.map((item, index) => ({
|
|
1291
1377
|
path: `/surveyItems/${index}`,
|
|
1292
1378
|
id: item.id,
|
|
1293
|
-
|
|
1379
|
+
editorName: names.get(item.id) ?? item.id,
|
|
1294
1380
|
itemType: item.itemType,
|
|
1295
1381
|
parentId: parentByChildId.get(item.id),
|
|
1296
1382
|
childCount: getGroupChildren(item).length || void 0
|
|
@@ -1313,10 +1399,27 @@ const buildValues = (snapshot, input) => {
|
|
|
1313
1399
|
const rawSurvey = getRawSurvey(snapshot);
|
|
1314
1400
|
return input.paths.map((path) => {
|
|
1315
1401
|
try {
|
|
1402
|
+
const value = getValueAtJsonPointer(rawSurvey, path);
|
|
1403
|
+
if (typeof value === "string" && (input.textOffset !== void 0 || JSON.stringify(value).length > input.valueCharacterLimit)) {
|
|
1404
|
+
const characters = [...value];
|
|
1405
|
+
const textOffset = input.textOffset ?? 0;
|
|
1406
|
+
const text = characters.slice(textOffset, textOffset + input.valueCharacterLimit).join("");
|
|
1407
|
+
const nextTextOffset = textOffset + [...text].length;
|
|
1408
|
+
return {
|
|
1409
|
+
path,
|
|
1410
|
+
found: true,
|
|
1411
|
+
text,
|
|
1412
|
+
textOffset,
|
|
1413
|
+
textLength: characters.length,
|
|
1414
|
+
truncated: textOffset > 0 || nextTextOffset < characters.length,
|
|
1415
|
+
...nextTextOffset < characters.length ? { nextTextOffset } : {},
|
|
1416
|
+
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."
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1316
1419
|
return {
|
|
1317
1420
|
path,
|
|
1318
1421
|
found: true,
|
|
1319
|
-
...toOutputValue(
|
|
1422
|
+
...toOutputValue(value, input.valueCharacterLimit)
|
|
1320
1423
|
};
|
|
1321
1424
|
} catch (error) {
|
|
1322
1425
|
return {
|
|
@@ -1334,7 +1437,7 @@ const findRawItemByIdentifier = (snapshot, identifier) => {
|
|
|
1334
1437
|
item: rawItems[directIndex],
|
|
1335
1438
|
index: directIndex
|
|
1336
1439
|
};
|
|
1337
|
-
const resolved = resolveItemSummary(identifier,
|
|
1440
|
+
const resolved = resolveItemSummary(identifier, mapItemIndexById(snapshot), mapOutlineById(snapshot));
|
|
1338
1441
|
if (!resolved) return;
|
|
1339
1442
|
const resolvedIndex = rawItems.findIndex((item) => item.id === resolved.itemId);
|
|
1340
1443
|
if (resolvedIndex < 0) return;
|
|
@@ -1358,11 +1461,12 @@ const buildItemValues = (snapshot, input) => {
|
|
|
1358
1461
|
if (!resolved) return {
|
|
1359
1462
|
identifier,
|
|
1360
1463
|
found: false,
|
|
1361
|
-
error: `No item matching "${identifier}" was found by item
|
|
1464
|
+
error: `No item matching "${identifier}" was found by exact item ID.`
|
|
1362
1465
|
};
|
|
1363
1466
|
const itemId = resolved.item.id;
|
|
1364
1467
|
const rawItemOutput = toOutputValue(resolved.item, input.valueCharacterLimit);
|
|
1365
|
-
const
|
|
1468
|
+
const translationsByLocale = getTranslationsByLocaleForItem(rawSurvey, itemId);
|
|
1469
|
+
const translationsOutput = toOutputValue(translationsByLocale, input.valueCharacterLimit);
|
|
1366
1470
|
const translationsValue = "value" in translationsOutput && isRecord$1(translationsOutput.value) ? translationsOutput.value : void 0;
|
|
1367
1471
|
return {
|
|
1368
1472
|
identifier,
|
|
@@ -1379,88 +1483,38 @@ const buildItemValues = (snapshot, input) => {
|
|
|
1379
1483
|
};
|
|
1380
1484
|
});
|
|
1381
1485
|
};
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
const
|
|
1385
|
-
if (
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
else lines.push(`${entry.path}: ${JSON.stringify(entry.value)}`);
|
|
1412
|
-
}
|
|
1413
|
-
return lines.join("\n");
|
|
1486
|
+
const inspectRawSurvey = (inputData, requestContext) => {
|
|
1487
|
+
const input = inputSchema$5.parse(inputData);
|
|
1488
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1489
|
+
if (!snapshot) return {
|
|
1490
|
+
ok: false,
|
|
1491
|
+
reason: "No live editor snapshot was provided with this assistant turn."
|
|
1492
|
+
};
|
|
1493
|
+
return parseInspectionOutput(outputSchema$4, {
|
|
1494
|
+
ok: true,
|
|
1495
|
+
revision: snapshot.version.revision,
|
|
1496
|
+
contentHash: snapshot.version.contentHash,
|
|
1497
|
+
listOrdering: {
|
|
1498
|
+
"overview.topLevelKeys": "Alphabetical key order; not survey content order.",
|
|
1499
|
+
"overview.locales": "Selected locale first, then remaining source locale order; locale position has no survey meaning.",
|
|
1500
|
+
"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.",
|
|
1501
|
+
values: "Same order as the requested JSON Pointer paths.",
|
|
1502
|
+
items: "Same order as the requested itemIdentifiers."
|
|
1503
|
+
},
|
|
1504
|
+
overview: input.paths || input.itemIdentifiers ? void 0 : buildOverview(snapshot),
|
|
1505
|
+
items: input.itemIdentifiers ? buildItemValues(snapshot, {
|
|
1506
|
+
itemIdentifiers: input.itemIdentifiers,
|
|
1507
|
+
valueCharacterLimit: input.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1508
|
+
}) : void 0,
|
|
1509
|
+
values: input.paths ? buildValues(snapshot, {
|
|
1510
|
+
paths: input.paths,
|
|
1511
|
+
textOffset: input.textOffset,
|
|
1512
|
+
valueCharacterLimit: input.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
|
|
1513
|
+
}) : void 0
|
|
1514
|
+
}, "Raw-survey inspection");
|
|
1414
1515
|
};
|
|
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
1516
|
//#endregion
|
|
1462
|
-
//#region src/server/tools/survey-
|
|
1463
|
-
const TOOL_ID$4 = "survey_search_items";
|
|
1517
|
+
//#region src/server/tools/search-survey-items.ts
|
|
1464
1518
|
const MAX_QUERY_LENGTH = 180;
|
|
1465
1519
|
const MAX_MATCHES_PER_ITEM = 6;
|
|
1466
1520
|
const DEFAULT_TEXT_LIMIT = 220;
|
|
@@ -1499,7 +1553,7 @@ const outputSchema$3 = z.object({
|
|
|
1499
1553
|
ordering: z.string().optional().describe("How results and their matches are ordered; this search output is not survey order."),
|
|
1500
1554
|
results: z.array(searchResultSchema).optional()
|
|
1501
1555
|
});
|
|
1502
|
-
const STOP_WORDS = new Set([
|
|
1556
|
+
const STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1503
1557
|
"a",
|
|
1504
1558
|
"about",
|
|
1505
1559
|
"an",
|
|
@@ -1552,7 +1606,7 @@ const candidateItems = (snapshot) => {
|
|
|
1552
1606
|
const rawItemsById = mapRawItemsById(snapshot);
|
|
1553
1607
|
const seen = /* @__PURE__ */ new Set();
|
|
1554
1608
|
const candidates = [];
|
|
1555
|
-
for (const entry of
|
|
1609
|
+
for (const entry of summarizeItemIndexEntries(snapshot.inspection.itemIndex, outlineById)) {
|
|
1556
1610
|
seen.add(entry.itemId);
|
|
1557
1611
|
candidates.push({
|
|
1558
1612
|
item: entry,
|
|
@@ -1576,8 +1630,8 @@ const searchItems = (snapshot, input) => {
|
|
|
1576
1630
|
const structuralFields = [
|
|
1577
1631
|
["itemId", item.itemId],
|
|
1578
1632
|
["itemType", item.itemType],
|
|
1579
|
-
["key", item.
|
|
1580
|
-
["
|
|
1633
|
+
["key", item.editorName],
|
|
1634
|
+
["breadcrumb", item.breadcrumb],
|
|
1581
1635
|
["itemLabel", item.itemLabel],
|
|
1582
1636
|
["path", item.path?.join(" ")]
|
|
1583
1637
|
];
|
|
@@ -1619,61 +1673,29 @@ const searchItems = (snapshot, input) => {
|
|
|
1619
1673
|
matches: matches.slice(0, MAX_MATCHES_PER_ITEM),
|
|
1620
1674
|
score: matches.reduce((sum, match) => sum + match.score, 0)
|
|
1621
1675
|
};
|
|
1622
|
-
}).filter((result) => Boolean(result)).sort((a, b) => b.score - a.score || a.item.
|
|
1676
|
+
}).filter((result) => Boolean(result)).sort((a, b) => b.score - a.score || a.item.breadcrumb.localeCompare(b.item.breadcrumb));
|
|
1623
1677
|
};
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1678
|
+
const searchSurveyItems = (inputData, requestContext) => {
|
|
1679
|
+
const input = inputSchema$4.parse(inputData);
|
|
1680
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1681
|
+
if (!snapshot) return {
|
|
1682
|
+
ok: false,
|
|
1683
|
+
reason: "No live editor snapshot was provided with this assistant turn.",
|
|
1684
|
+
query: input.query
|
|
1685
|
+
};
|
|
1686
|
+
const results = searchItems(snapshot, input);
|
|
1687
|
+
const page = sliceWithCursor(results, input);
|
|
1688
|
+
return parseInspectionOutput(outputSchema$3, {
|
|
1689
|
+
ok: true,
|
|
1690
|
+
query: input.query,
|
|
1691
|
+
totalAvailable: results.length,
|
|
1692
|
+
returned: page.items.length,
|
|
1693
|
+
nextCursor: page.nextCursor,
|
|
1694
|
+
truncated: page.truncated,
|
|
1695
|
+
ordering: "Results are ranked by descending text-match score, then structural breadcrumb. Matches within each result are ranked by descending score. Neither order is survey or respondent-visible order.",
|
|
1696
|
+
results: page.items
|
|
1697
|
+
}, "Survey-item search");
|
|
1638
1698
|
};
|
|
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
1699
|
//#endregion
|
|
1678
1700
|
//#region src/server/tools/survey-inspect-tool.ts
|
|
1679
1701
|
const TOOL_ID$3 = "survey_inspect";
|
|
@@ -1707,14 +1729,15 @@ const inputSchema$3 = z.object({
|
|
|
1707
1729
|
"ui-context",
|
|
1708
1730
|
"history"
|
|
1709
1731
|
])).min(1).max(6)).describe("For current-context: context sections to inspect. Empty means omitted."),
|
|
1710
|
-
query: optionalNonBlankString(180).describe("For search-items: text to search across survey content. For assets: optional asset id, filename, name, ALT text, or media type filter. For current-context outline: optional
|
|
1732
|
+
query: optionalNonBlankString(180).describe("For search-items: text to search across survey content. For assets: optional asset id, filename, name, ALT text, or media type filter. For current-context outline: optional editor-name/breadcrumb/label filter."),
|
|
1711
1733
|
sourceLocale: optionalNonBlankString(40).describe("For localization: source locale to translate. Defaults to the selected editor locale."),
|
|
1712
1734
|
targetLocale: optionalNonBlankString(40).describe("For localization: required target locale whose coverage should be inspected."),
|
|
1713
1735
|
missingOnly: optionalInput(z.boolean()).describe("For localization: return only source surfaces that do not yet exist in the target locale."),
|
|
1714
|
-
itemRef: optionalNonBlankString(240).describe("For current-context outline, item-details, or raw-json: item id, key,
|
|
1715
|
-
cursor: optionalNonBlankString(240).describe("For current-context, search-items, or localization pagination: numeric cursor from a previous result."),
|
|
1716
|
-
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
|
-
paths: optionalArrayInput(z.array(z.string().
|
|
1736
|
+
itemRef: optionalNonBlankString(240).describe("For current-context outline, item-details, or raw-json: item id, key, breadcrumb, visible label, or \"selected\"."),
|
|
1737
|
+
cursor: optionalNonBlankString(240).describe("For current-context, item-details response variables, search-items, or localization pagination: numeric cursor from a previous result."),
|
|
1738
|
+
outlineMode: optionalInput(outlineModeSchema).describe("For current-context outline: \"full\" pages all survey items, \"focused\" returns the selected/focused neighborhood, and \"children\" returns direct children of itemRef in stored sibling order. To verify placement or prepare a reorder, use \"children\" with the parent itemRef and page until complete; shuffled groups may render a different respondent order."),
|
|
1739
|
+
paths: optionalArrayInput(z.array(z.string().max(500)).min(1).max(MAX_RAW_JSON_PATHS_PER_REQUEST)).describe(`For raw-json: exact JSON Pointer paths to inspect in request order. itemRef takes precedence when both are supplied. Up to ${MAX_RAW_JSON_PATHS_PER_RESULT} paths are returned per call; larger valid requests receive explicit coverage and retry guidance instead of failing input validation. An empty paths array means omitted; a path of "" selects the document root (JSON Pointer), while "/" selects an empty property name.`),
|
|
1740
|
+
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
1741
|
includeChildren: optionalInput(z.boolean()).describe("For item-details: include direct survey-tree children."),
|
|
1719
1742
|
includeItemConfig: optionalInput(z.boolean()).describe("For item-details: include choice/form/internal item config details."),
|
|
1720
1743
|
limit: optionalInput(z.number().int().min(1).max(50)).describe("Maximum list rows to return.")
|
|
@@ -1726,19 +1749,31 @@ const outputSchema$2 = z.object({
|
|
|
1726
1749
|
modelSummary: z.string(),
|
|
1727
1750
|
result: z.unknown().optional()
|
|
1728
1751
|
});
|
|
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
1752
|
const stringifyForModel = (value) => {
|
|
1739
1753
|
if (typeof value === "string") return value;
|
|
1740
1754
|
return JSON.stringify(value, null, 2);
|
|
1741
1755
|
};
|
|
1756
|
+
const runInspectionBackend = (inspect) => {
|
|
1757
|
+
try {
|
|
1758
|
+
return {
|
|
1759
|
+
ok: true,
|
|
1760
|
+
value: inspect()
|
|
1761
|
+
};
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
if (!(error instanceof z.ZodError)) throw error;
|
|
1764
|
+
const firstIssue = error.issues[0];
|
|
1765
|
+
return {
|
|
1766
|
+
ok: false,
|
|
1767
|
+
reason: `Survey inspection validation failed${firstIssue?.path?.length ? ` at ${firstIssue.path.join(".").slice(0, 120)}` : ""}: ${(firstIssue?.message ?? "Invalid internal inspection data.").slice(0, 400)}`
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
const inspectionBackendFailure = (action, reason) => ({
|
|
1772
|
+
ok: false,
|
|
1773
|
+
action,
|
|
1774
|
+
reason,
|
|
1775
|
+
modelSummary: reason
|
|
1776
|
+
});
|
|
1742
1777
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1743
1778
|
const MAX_LOCALIZATION_TEXT_CHARS = 2e3;
|
|
1744
1779
|
const SURVEY_TRANSLATION_SURFACES = [
|
|
@@ -1752,6 +1787,7 @@ const truncateLocalizationText = (content) => {
|
|
|
1752
1787
|
};
|
|
1753
1788
|
const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
1754
1789
|
const survey = snapshot.survey;
|
|
1790
|
+
const names = new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item.editorName]));
|
|
1755
1791
|
const sourceLocale = input.sourceLocale ?? snapshot.ui.selectedLocale;
|
|
1756
1792
|
const sourceTranslations = survey.translations?.[sourceLocale];
|
|
1757
1793
|
const targetTranslations = survey.translations?.[targetLocale];
|
|
@@ -1763,6 +1799,7 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1763
1799
|
const addSurface = ({ contentKey, itemId, itemKey, itemType, sourceContent, surface, targetContent }) => {
|
|
1764
1800
|
const sourceText = truncateLocalizationText(sourceContent);
|
|
1765
1801
|
if (!sourceText) return;
|
|
1802
|
+
const sourcePath = `/translations/${encodeJsonPointerSegment(sourceLocale)}/${itemId ? `itemTranslations/${encodeJsonPointerSegment(itemId)}` : surface}/${encodeJsonPointerSegment(contentKey)}`;
|
|
1766
1803
|
surfaces.push({
|
|
1767
1804
|
scope: itemId ? "item" : "survey",
|
|
1768
1805
|
...surface ? { surface } : {},
|
|
@@ -1774,11 +1811,17 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1774
1811
|
contentKey,
|
|
1775
1812
|
sourceType: sourceContent.type,
|
|
1776
1813
|
sourceText,
|
|
1814
|
+
sourceTextLength: getContentPlainText(sourceContent).trim().length,
|
|
1815
|
+
sourceTruncated: getContentPlainText(sourceContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS,
|
|
1816
|
+
sourcePath,
|
|
1817
|
+
...sourceContent.type !== "richText" ? { sourceTextPath: `${sourcePath}/content` } : {},
|
|
1777
1818
|
...sourceContent.type === "richText" ? { sourceContent } : {},
|
|
1778
1819
|
targetPresent: Boolean(targetContent && truncateLocalizationText(targetContent)),
|
|
1779
1820
|
...targetContent ? {
|
|
1780
1821
|
targetType: targetContent.type,
|
|
1781
|
-
targetText: truncateLocalizationText(targetContent)
|
|
1822
|
+
targetText: truncateLocalizationText(targetContent),
|
|
1823
|
+
targetTextLength: getContentPlainText(targetContent).trim().length,
|
|
1824
|
+
targetTruncated: getContentPlainText(targetContent).trim().length > MAX_LOCALIZATION_TEXT_CHARS
|
|
1782
1825
|
} : {}
|
|
1783
1826
|
});
|
|
1784
1827
|
};
|
|
@@ -1793,7 +1836,7 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1793
1836
|
}
|
|
1794
1837
|
for (const item of survey.surveyItems) for (const [contentKey, sourceContent] of Object.entries(sourceTranslations.itemTranslations?.[item.id] ?? {})) addSurface({
|
|
1795
1838
|
itemId: item.id,
|
|
1796
|
-
itemKey: item.
|
|
1839
|
+
itemKey: names.get(item.id) ?? item.id,
|
|
1797
1840
|
itemType: item.itemType,
|
|
1798
1841
|
contentKey,
|
|
1799
1842
|
sourceContent,
|
|
@@ -1812,6 +1855,7 @@ const listLocalizationSurfaces = (input, snapshot, targetLocale) => {
|
|
|
1812
1855
|
sourceLocale,
|
|
1813
1856
|
targetLocale,
|
|
1814
1857
|
missingOnly: input.missingOnly ?? false,
|
|
1858
|
+
contentCoverage: "sourceText and targetText are previews. For sourceTruncated plain text, inspect raw-json paths: [sourceTextPath] and follow nextTextOffset with textOffset until complete before translating. Rich-text sourceContent is the complete canonical value. Never translate only a truncated preview or treat targetPresent as proof of translation completeness.",
|
|
1815
1859
|
totalSourceSurfaces: surfaces.length,
|
|
1816
1860
|
totalAvailable: filteredSurfaces.length,
|
|
1817
1861
|
missingCount,
|
|
@@ -1903,7 +1947,7 @@ const listAssets = (input, snapshot) => {
|
|
|
1903
1947
|
};
|
|
1904
1948
|
const surveyInspectTool = createTool({
|
|
1905
1949
|
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.",
|
|
1950
|
+
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
1951
|
inputSchema: inputSchema$3,
|
|
1908
1952
|
outputSchema: outputSchema$2,
|
|
1909
1953
|
mcp: { annotations: {
|
|
@@ -1915,7 +1959,8 @@ const surveyInspectTool = createTool({
|
|
|
1915
1959
|
} },
|
|
1916
1960
|
execute: async (inputData, context) => {
|
|
1917
1961
|
const input = inputData;
|
|
1918
|
-
const
|
|
1962
|
+
const requestContext = context?.requestContext;
|
|
1963
|
+
const snapshot = getSurveyAssistantEngineContext(requestContext);
|
|
1919
1964
|
if (!snapshot) return {
|
|
1920
1965
|
ok: false,
|
|
1921
1966
|
action: input.action,
|
|
@@ -1923,14 +1968,16 @@ const surveyInspectTool = createTool({
|
|
|
1923
1968
|
modelSummary: "No survey draft is available for this turn."
|
|
1924
1969
|
};
|
|
1925
1970
|
if (input.action === "current-context") {
|
|
1926
|
-
const
|
|
1971
|
+
const inspection = runInspectionBackend(() => inspectSurveyCurrentContext({
|
|
1927
1972
|
scopes: input.scopes ?? ["summary", "selected-item"],
|
|
1928
1973
|
limit: input.limit,
|
|
1929
1974
|
cursor: input.cursor,
|
|
1930
1975
|
itemId: resolveItemRef(input, snapshot),
|
|
1931
1976
|
query: input.query,
|
|
1932
1977
|
outlineMode: input.outlineMode
|
|
1933
|
-
},
|
|
1978
|
+
}, requestContext));
|
|
1979
|
+
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
1980
|
+
const result = inspection.value;
|
|
1934
1981
|
return {
|
|
1935
1982
|
ok: true,
|
|
1936
1983
|
action: input.action,
|
|
@@ -1945,11 +1992,14 @@ const surveyInspectTool = createTool({
|
|
|
1945
1992
|
reason: "Search requires a query.",
|
|
1946
1993
|
modelSummary: "Search requires a query."
|
|
1947
1994
|
};
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1995
|
+
const query = input.query;
|
|
1996
|
+
const inspection = runInspectionBackend(() => searchSurveyItems({
|
|
1997
|
+
query,
|
|
1950
1998
|
limit: input.limit,
|
|
1951
1999
|
cursor: input.cursor
|
|
1952
|
-
},
|
|
2000
|
+
}, requestContext));
|
|
2001
|
+
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
2002
|
+
const result = inspection.value;
|
|
1953
2003
|
return {
|
|
1954
2004
|
ok: true,
|
|
1955
2005
|
action: input.action,
|
|
@@ -1965,12 +2015,16 @@ const surveyInspectTool = createTool({
|
|
|
1965
2015
|
reason: "No item reference was provided and no item is selected.",
|
|
1966
2016
|
modelSummary: "No item reference was provided and no item is selected."
|
|
1967
2017
|
};
|
|
1968
|
-
const
|
|
2018
|
+
const inspection = runInspectionBackend(() => inspectSurveyItemDetails({
|
|
1969
2019
|
itemId,
|
|
1970
2020
|
includeSurveyChildren: input.includeChildren ?? false,
|
|
1971
2021
|
includeItemConfig: input.includeItemConfig ?? true,
|
|
1972
|
-
childrenLimit: input.limit
|
|
1973
|
-
|
|
2022
|
+
childrenLimit: input.limit,
|
|
2023
|
+
responseVariablesLimit: input.limit,
|
|
2024
|
+
cursor: input.cursor
|
|
2025
|
+
}, requestContext));
|
|
2026
|
+
if (!inspection.ok) return inspectionBackendFailure(input.action, inspection.reason);
|
|
2027
|
+
const result = inspection.value;
|
|
1974
2028
|
return {
|
|
1975
2029
|
ok: true,
|
|
1976
2030
|
action: input.action,
|
|
@@ -1979,20 +2033,35 @@ const surveyInspectTool = createTool({
|
|
|
1979
2033
|
};
|
|
1980
2034
|
}
|
|
1981
2035
|
if (input.action === "expression-diagnostics") {
|
|
2036
|
+
const visibilityIssues = getAssistantVisibilityIssues(createAssistantSurvey(snapshot.survey, snapshot.trustedCapabilities));
|
|
1982
2037
|
const allDiagnostics = analyzeSurveyAssistantExpressionReferences(snapshot.survey, snapshot.trustedCapabilities);
|
|
1983
2038
|
const itemId = input.itemRef ? resolveItemRef(input, snapshot) : void 0;
|
|
1984
2039
|
const diagnostics = itemId ? allDiagnostics.filter((diagnostic) => diagnostic.ownerItemId === itemId) : allDiagnostics;
|
|
2040
|
+
const orderedDiagnostics = [...diagnostics].sort((left, right) => Number(isBlockingSurveyExpressionReferenceDiagnostic(right)) - Number(isBlockingSurveyExpressionReferenceDiagnostic(left)));
|
|
2041
|
+
const blockingCount = diagnostics.filter(isBlockingSurveyExpressionReferenceDiagnostic).length;
|
|
2042
|
+
const advisoryCount = diagnostics.length - blockingCount;
|
|
1985
2043
|
const limit = input.limit ?? 50;
|
|
2044
|
+
const limitedDiagnostics = orderedDiagnostics.slice(0, limit);
|
|
2045
|
+
const guidanceByDispositionAndCategory = {};
|
|
2046
|
+
for (const diagnostic of orderedDiagnostics) {
|
|
2047
|
+
const byCategory = guidanceByDispositionAndCategory[diagnostic.disposition] ??= {};
|
|
2048
|
+
byCategory[diagnostic.category] = getSurveyAssistantExpressionDiagnosticGuidance(diagnostic);
|
|
2049
|
+
}
|
|
1986
2050
|
const result = {
|
|
1987
|
-
valid:
|
|
2051
|
+
valid: blockingCount === 0 && visibilityIssues.length === 0,
|
|
2052
|
+
visibilityIssues,
|
|
2053
|
+
visibilityScope: "survey",
|
|
1988
2054
|
count: diagnostics.length,
|
|
1989
|
-
|
|
2055
|
+
blockingCount,
|
|
2056
|
+
advisoryCount,
|
|
2057
|
+
diagnostics: limitedDiagnostics,
|
|
2058
|
+
guidanceByDispositionAndCategory,
|
|
1990
2059
|
truncated: diagnostics.length > limit
|
|
1991
2060
|
};
|
|
1992
2061
|
return {
|
|
1993
2062
|
ok: true,
|
|
1994
2063
|
action: input.action,
|
|
1995
|
-
modelSummary: diagnostics.length === 0 ? "No
|
|
2064
|
+
modelSummary: visibilityIssues.length > 0 ? `Survey-wide visibility validation failed. Repair cyclic or callback-based visibility using independent controlling answers or serialized context values. These checks include ancestor, component and computed dependencies outside any item filter.\n${stringifyForModel(result)}` : diagnostics.length === 0 ? "No expression reference diagnostics were found in the current survey draft." : blockingCount === 0 ? `${advisoryCount} advisory expression diagnostic${advisoryCount === 1 ? " was" : "s were"} found. Review each listed diagnostic according to its category-specific guidance.\n${stringifyForModel(result)}` : `${blockingCount} blocking expression reference diagnostic${blockingCount === 1 ? " was" : "s were"} found${advisoryCount > 0 ? ` alongside ${advisoryCount} advisory expression diagnostic${advisoryCount === 1 ? "" : "s"}` : ""}. Repair every blocking location, preserve advisories unless their category-specific guidance or the user requires a change, then run expression-diagnostics again before claiming the blocking repair is complete.\n${stringifyForModel(result)}`,
|
|
1996
2065
|
result
|
|
1997
2066
|
};
|
|
1998
2067
|
}
|
|
@@ -2030,10 +2099,13 @@ const surveyInspectTool = createTool({
|
|
|
2030
2099
|
const itemIdentifiers = itemId ? [itemId] : void 0;
|
|
2031
2100
|
const requestedPaths = itemIdentifiers ? void 0 : input.paths;
|
|
2032
2101
|
const boundedPaths = requestedPaths?.slice(0, MAX_RAW_JSON_PATHS_PER_RESULT);
|
|
2033
|
-
const
|
|
2102
|
+
const rawInspection = runInspectionBackend(() => inspectRawSurvey({
|
|
2034
2103
|
paths: boundedPaths,
|
|
2035
|
-
itemIdentifiers
|
|
2036
|
-
|
|
2104
|
+
itemIdentifiers,
|
|
2105
|
+
textOffset: input.textOffset
|
|
2106
|
+
}, requestContext));
|
|
2107
|
+
if (!rawInspection.ok) return inspectionBackendFailure(input.action, rawInspection.reason);
|
|
2108
|
+
const rawResult = rawInspection.value;
|
|
2037
2109
|
const ignoredPathCount = itemIdentifiers ? input.paths?.length ?? 0 : 0;
|
|
2038
2110
|
const omittedPathCount = Math.max(0, (requestedPaths?.length ?? 0) - (boundedPaths?.length ?? 0));
|
|
2039
2111
|
const requestCoverage = ignoredPathCount > 0 || omittedPathCount > 0 ? {
|
|
@@ -2042,7 +2114,7 @@ const surveyInspectTool = createTool({
|
|
|
2042
2114
|
inspectedPathCount: boundedPaths?.length ?? 0,
|
|
2043
2115
|
ignoredPathCount,
|
|
2044
2116
|
omittedPathCount,
|
|
2045
|
-
...omittedPathCount > 0 ? { nextPaths: requestedPaths?.slice(MAX_RAW_JSON_PATHS_PER_RESULT,
|
|
2117
|
+
...omittedPathCount > 0 ? { nextPaths: requestedPaths?.slice(MAX_RAW_JSON_PATHS_PER_RESULT, 40) } : {},
|
|
2046
2118
|
recovery: itemIdentifiers ? "Use a separate raw-json call without itemRef if the explicit paths are still needed." : "Retry the returned nextPaths in a separate raw-json call if they are still needed."
|
|
2047
2119
|
} : void 0;
|
|
2048
2120
|
const result = requestCoverage && isRecord(rawResult) ? {
|
|
@@ -2062,33 +2134,45 @@ const surveyInspectTool = createTool({
|
|
|
2062
2134
|
//#endregion
|
|
2063
2135
|
//#region src/server/tools/survey-attachment-tool.ts
|
|
2064
2136
|
const TOOL_ID$2 = "survey_attachment";
|
|
2137
|
+
const inputSchema$2 = z.object({
|
|
2138
|
+
documentIds: z.array(z.string().min(1)).min(1).max(4).optional().describe("Stored attachment IDs to load completely when no page/section focus is needed."),
|
|
2139
|
+
documentRequests: z.array(z.object({
|
|
2140
|
+
documentId: z.string().min(1),
|
|
2141
|
+
pages: z.object({
|
|
2142
|
+
start: z.number().int().min(1).max(1e5),
|
|
2143
|
+
end: z.number().int().min(1).max(1e5)
|
|
2144
|
+
}).optional().describe("One-based inclusive PDF page range to prioritize."),
|
|
2145
|
+
focus: z.string().min(1).max(300).optional().describe("Short section, question range, or fact to prioritize in the original file.")
|
|
2146
|
+
})).min(1).max(4).optional().describe("Stored attachments with an optional bounded reading focus.")
|
|
2147
|
+
});
|
|
2148
|
+
const outputSchema$1 = z.object({
|
|
2149
|
+
loaded: z.array(z.object({
|
|
2150
|
+
id: z.string(),
|
|
2151
|
+
filename: z.string(),
|
|
2152
|
+
mediaType: z.string(),
|
|
2153
|
+
pages: z.object({
|
|
2154
|
+
start: z.number().int(),
|
|
2155
|
+
end: z.number().int()
|
|
2156
|
+
}).optional(),
|
|
2157
|
+
focus: z.string().optional()
|
|
2158
|
+
})),
|
|
2159
|
+
unavailable: z.array(z.object({
|
|
2160
|
+
id: z.string(),
|
|
2161
|
+
filename: z.string().optional(),
|
|
2162
|
+
mediaType: z.string().optional(),
|
|
2163
|
+
reason: z.enum([
|
|
2164
|
+
"not-listed",
|
|
2165
|
+
"content-unavailable",
|
|
2166
|
+
"attachment-limit"
|
|
2167
|
+
])
|
|
2168
|
+
})).optional(),
|
|
2169
|
+
warnings: z.array(z.string()).optional()
|
|
2170
|
+
});
|
|
2065
2171
|
const surveyAttachmentTool = createTool({
|
|
2066
2172
|
id: TOOL_ID$2,
|
|
2067
2173
|
description: "Attach only the stored PDFs or images needed for the current task to the next native model request. For source continuation, use this before conversational recall and provide a page or section focus when known. The original file remains authoritative.",
|
|
2068
|
-
inputSchema:
|
|
2069
|
-
|
|
2070
|
-
documentRequests: z.array(z.object({
|
|
2071
|
-
documentId: z.string().min(1),
|
|
2072
|
-
pages: z.object({
|
|
2073
|
-
start: z.number().int().min(1).max(1e5),
|
|
2074
|
-
end: z.number().int().min(1).max(1e5)
|
|
2075
|
-
}).optional().describe("One-based inclusive PDF page range to prioritize."),
|
|
2076
|
-
focus: z.string().min(1).max(300).optional().describe("Short section, question range, or fact to prioritize in the original file.")
|
|
2077
|
-
})).min(1).max(4).optional().describe("Stored attachments with an optional bounded reading focus.")
|
|
2078
|
-
}),
|
|
2079
|
-
outputSchema: z.object({
|
|
2080
|
-
loaded: z.array(z.object({
|
|
2081
|
-
id: z.string(),
|
|
2082
|
-
filename: z.string(),
|
|
2083
|
-
mediaType: z.string(),
|
|
2084
|
-
pages: z.object({
|
|
2085
|
-
start: z.number().int(),
|
|
2086
|
-
end: z.number().int()
|
|
2087
|
-
}).optional(),
|
|
2088
|
-
focus: z.string().optional()
|
|
2089
|
-
})),
|
|
2090
|
-
warnings: z.array(z.string()).optional()
|
|
2091
|
-
}),
|
|
2174
|
+
inputSchema: inputSchema$2,
|
|
2175
|
+
outputSchema: outputSchema$1,
|
|
2092
2176
|
mcp: { annotations: {
|
|
2093
2177
|
title: "Load chat attachment",
|
|
2094
2178
|
readOnlyHint: true,
|
|
@@ -2097,8 +2181,8 @@ const surveyAttachmentTool = createTool({
|
|
|
2097
2181
|
openWorldHint: false
|
|
2098
2182
|
} },
|
|
2099
2183
|
execute: async (inputData, context) => {
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2184
|
+
const requestContext = context?.requestContext;
|
|
2185
|
+
const access = requestContext?.get("attachmentAccess");
|
|
2102
2186
|
const warnings = [];
|
|
2103
2187
|
const requests = /* @__PURE__ */ new Map();
|
|
2104
2188
|
for (const documentId of inputData.documentIds ?? []) requests.set(documentId, {});
|
|
@@ -2114,11 +2198,50 @@ const surveyAttachmentTool = createTool({
|
|
|
2114
2198
|
});
|
|
2115
2199
|
}
|
|
2116
2200
|
const requestedIds = [...requests.keys()];
|
|
2117
|
-
const authorized = access
|
|
2118
|
-
const
|
|
2119
|
-
|
|
2201
|
+
const authorized = access?.documents.filter((document) => requests.has(document.id)) ?? [];
|
|
2202
|
+
const unavailable = requestedIds.filter((id) => !authorized.some((document) => document.id === id)).map((id) => ({
|
|
2203
|
+
id,
|
|
2204
|
+
reason: "not-listed"
|
|
2205
|
+
}));
|
|
2206
|
+
for (const { id } of unavailable) warnings.push(`Stored attachment ${id} is not in the available manifest.`);
|
|
2207
|
+
if (!access) return {
|
|
2208
|
+
loaded: [],
|
|
2209
|
+
unavailable,
|
|
2210
|
+
warnings
|
|
2211
|
+
};
|
|
2212
|
+
const attachments = await access.load(authorized.filter((document) => !access.selectedAttachments.some((attachment) => attachment.documentId === document.id)).map((document) => document.id));
|
|
2213
|
+
const selected = new Map(access.selectedAttachments.map((attachment) => [attachment.documentId, attachment]));
|
|
2214
|
+
const nativePayloads = new Map([...requestContext?.get("modelAttachments") ?? [], ...selected.values()].map((attachment) => [`${attachment.mediaType}\u0000${attachment.contentHash}`, Buffer.byteLength(attachment.data, "base64")]));
|
|
2215
|
+
let totalBytes = [...nativePayloads.values()].reduce((sum, bytes) => sum + bytes, 0);
|
|
2216
|
+
for (const document of authorized) {
|
|
2217
|
+
if (selected.has(document.id)) continue;
|
|
2218
|
+
const attachment = attachments.find((candidate) => candidate.documentId === document.id);
|
|
2219
|
+
let reason = "content-unavailable";
|
|
2220
|
+
if (attachment) {
|
|
2221
|
+
const key = `${attachment.mediaType}\u0000${attachment.contentHash}`;
|
|
2222
|
+
const bytes = Buffer.byteLength(attachment.data, "base64");
|
|
2223
|
+
const alreadyIncluded = nativePayloads.has(key);
|
|
2224
|
+
if (bytes <= access.limits.maxAttachmentBytes && (alreadyIncluded || nativePayloads.size < access.limits.maxAttachments && totalBytes + bytes <= access.limits.maxTotalAttachmentBytes)) {
|
|
2225
|
+
selected.set(document.id, attachment);
|
|
2226
|
+
if (!alreadyIncluded) {
|
|
2227
|
+
nativePayloads.set(key, bytes);
|
|
2228
|
+
totalBytes += bytes;
|
|
2229
|
+
}
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
reason = "attachment-limit";
|
|
2233
|
+
}
|
|
2234
|
+
unavailable.push({
|
|
2235
|
+
id: document.id,
|
|
2236
|
+
filename: document.name,
|
|
2237
|
+
mediaType: document.mediaType,
|
|
2238
|
+
reason
|
|
2239
|
+
});
|
|
2240
|
+
warnings.push(reason === "attachment-limit" ? `Stored attachment ${document.name} (${document.id}) exceeds this turn's attachment limit and was not loaded. Use a new turn with fewer or smaller sources.` : `Stored attachment ${document.name} (${document.id}) is unavailable. No content was loaded for it. Retry loading it or ask the user to upload it again.`);
|
|
2241
|
+
}
|
|
2242
|
+
access.selectedAttachments.splice(0, access.selectedAttachments.length, ...selected.values());
|
|
2120
2243
|
return {
|
|
2121
|
-
loaded: authorized.map((document) => {
|
|
2244
|
+
loaded: authorized.filter((document) => selected.has(document.id)).map((document) => {
|
|
2122
2245
|
const request = requests.get(document.id);
|
|
2123
2246
|
const pages = document.mediaType === "application/pdf" ? request?.pages : void 0;
|
|
2124
2247
|
if (request?.pages && !pages) warnings.push(`Ignored a PDF page focus for non-PDF attachment ${document.name}.`);
|
|
@@ -2130,6 +2253,7 @@ const surveyAttachmentTool = createTool({
|
|
|
2130
2253
|
...request?.focus ? { focus: request.focus } : {}
|
|
2131
2254
|
};
|
|
2132
2255
|
}),
|
|
2256
|
+
...unavailable.length > 0 ? { unavailable } : {},
|
|
2133
2257
|
...warnings.length > 0 ? { warnings } : {}
|
|
2134
2258
|
};
|
|
2135
2259
|
},
|
|
@@ -2204,7 +2328,7 @@ const formatAuditForModel = (output) => {
|
|
|
2204
2328
|
finding.locale,
|
|
2205
2329
|
finding.contentKey
|
|
2206
2330
|
].filter(Boolean).join(":");
|
|
2207
|
-
return `- ${finding.code}${location ? ` (${location})` : ""}: ${previewAuditMessage(finding.message)}`;
|
|
2331
|
+
return `- [${finding.severity}] ${finding.code}${location ? ` (${location})` : ""}: ${previewAuditMessage(finding.message)}${finding.suggestion ? ` Guidance: ${previewAuditMessage(finding.suggestion)}` : ""}`;
|
|
2208
2332
|
}).join("\n")}` : void 0,
|
|
2209
2333
|
report.qualityReviewPrinciples.length > 0 ? `Qualitative review rubric:\n${report.qualityReviewPrinciples.map((principle) => `- ${principle.id}: ${principle.prompt}`).join("\n")}` : void 0,
|
|
2210
2334
|
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 +2368,7 @@ const surveyAuditTool = createTool({
|
|
|
2244
2368
|
//#region src/server/tools/survey-asset-tool.ts
|
|
2245
2369
|
const TOOL_ID = "survey_asset";
|
|
2246
2370
|
const inputSchema = z.object({
|
|
2371
|
+
retryOfChangeIds: retryOfChangeIdsSchema,
|
|
2247
2372
|
action: z.enum([
|
|
2248
2373
|
"import-attachment",
|
|
2249
2374
|
"update-metadata",
|
|
@@ -2293,12 +2418,13 @@ const summarizeAssetChange = (event) => {
|
|
|
2293
2418
|
lines.push(`Imported: ${event.operations.filter((operation) => operation.kind === "set-image-asset").map(describeAssetOperation).join(", ")}`);
|
|
2294
2419
|
lines.push(`Asset id to reference: ${importedAssetIds.join(", ")}`);
|
|
2295
2420
|
lines.push("The asset is now available to later mutation calls in this turn. To use it in a new item, reference it from that item's create-item translation. If a later delta is rejected, fix only that delta; this import remains in the draft.");
|
|
2296
|
-
lines.push(`New info-item image source: { "type": "asset", "assetId": "${importedAssetIds[0]}" }. Embed
|
|
2297
|
-
lines.push(`New info-item translation example: { "locale": "<selected-locale>", "contentKey": "content", "
|
|
2421
|
+
lines.push(`New info-item image source: { "type": "asset", "assetId": "${importedAssetIds[0]}" }. Embed the canonical image block inside the create-item translation richTextBlocks array.`);
|
|
2422
|
+
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
2423
|
}
|
|
2299
2424
|
}
|
|
2300
2425
|
if (event.validation.issues.length > 0) lines.push(`Issues:\n${event.validation.issues.map((issue) => `- ${issue.code}: ${issue.message}`).join("\n")}`);
|
|
2301
2426
|
if (event.validation.warnings.length > 0) lines.push(`Warnings:\n${event.validation.warnings.map((warning) => `- ${warning.code}: ${warning.message}`).join("\n")}`);
|
|
2427
|
+
lines.push(...summarizePendingRepairs(event));
|
|
2302
2428
|
return lines.join("\n");
|
|
2303
2429
|
};
|
|
2304
2430
|
const redactEmbeddedImageOperation = (operation) => operation.kind === "set-image-asset" ? {
|
|
@@ -2342,7 +2468,7 @@ const surveyAssetTool = createTool({
|
|
|
2342
2468
|
const manifest = access?.documents.find((entry) => entry.id === input.documentId);
|
|
2343
2469
|
if (!access || !manifest) throw new Error(`Attachment "${input.documentId}" is not available for this turn.`);
|
|
2344
2470
|
if (!manifest.mediaType.startsWith("image/")) throw new Error(`Attachment "${manifest.name}" is not an image.`);
|
|
2345
|
-
const
|
|
2471
|
+
const loaded = (await access.load([input.documentId])).find((document) => document.documentId === input.documentId);
|
|
2346
2472
|
if (!loaded) throw new Error(`Attachment "${manifest.name}" could not be loaded.`);
|
|
2347
2473
|
const base = input.assetId ?? slug(manifest.name);
|
|
2348
2474
|
let assetId = base;
|
|
@@ -2408,9 +2534,10 @@ const surveyAssetTool = createTool({
|
|
|
2408
2534
|
normalizedOperations: []
|
|
2409
2535
|
}
|
|
2410
2536
|
});
|
|
2411
|
-
if (event.status === "rejected") return turnDraft.reject(event, [
|
|
2537
|
+
if (event.status === "rejected") return turnDraft.reject(event, collectSurveyOperationRecoveryTargets([operation], snapshot));
|
|
2412
2538
|
}
|
|
2413
2539
|
return turnDraft.stage({
|
|
2540
|
+
retryOfChangeIds: input.retryOfChangeIds,
|
|
2414
2541
|
operations: [operation],
|
|
2415
2542
|
snapshot,
|
|
2416
2543
|
summary: input.summary
|
|
@@ -2423,18 +2550,18 @@ const surveyAssetTool = createTool({
|
|
|
2423
2550
|
//#region src/server/tools/index.ts
|
|
2424
2551
|
const SURVEY_ASSISTANT_TOOL_IDS = [
|
|
2425
2552
|
TOOL_ID$3,
|
|
2426
|
-
TOOL_ID$
|
|
2427
|
-
TOOL_ID$
|
|
2428
|
-
TOOL_ID$
|
|
2553
|
+
TOOL_ID$5,
|
|
2554
|
+
TOOL_ID$4,
|
|
2555
|
+
TOOL_ID$6,
|
|
2429
2556
|
TOOL_ID$2,
|
|
2430
2557
|
TOOL_ID$1,
|
|
2431
2558
|
TOOL_ID
|
|
2432
2559
|
];
|
|
2433
2560
|
const surveyAssistantTools = Object.freeze({
|
|
2434
2561
|
[TOOL_ID$3]: surveyInspectTool,
|
|
2435
|
-
[TOOL_ID$
|
|
2436
|
-
[TOOL_ID$
|
|
2437
|
-
[TOOL_ID$
|
|
2562
|
+
[TOOL_ID$5]: surveyChangeTool,
|
|
2563
|
+
[TOOL_ID$6]: surveyAuthoringReferenceTool,
|
|
2564
|
+
[TOOL_ID$4]: surveyExpressionTool,
|
|
2438
2565
|
[TOOL_ID$2]: surveyAttachmentTool,
|
|
2439
2566
|
[TOOL_ID$1]: surveyAuditTool,
|
|
2440
2567
|
[TOOL_ID]: surveyAssetTool
|
|
@@ -2442,4 +2569,4 @@ const surveyAssistantTools = Object.freeze({
|
|
|
2442
2569
|
//#endregion
|
|
2443
2570
|
export { surveyAuditTool as i, surveyAssistantTools as n, surveyAssetTool as r, SURVEY_ASSISTANT_TOOL_IDS as t };
|
|
2444
2571
|
|
|
2445
|
-
//# sourceMappingURL=tools-
|
|
2572
|
+
//# sourceMappingURL=tools-CEsv-WoO.mjs.map
|