@case-framework/survey-assistant 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/{capabilities-DZ0a_o2y.mjs → capabilities-DKbNCnBf.mjs} +2 -2
  2. package/dist/{capabilities-DZ0a_o2y.mjs.map → capabilities-DKbNCnBf.mjs.map} +1 -1
  3. package/dist/capabilities-default.d.mts +1 -1
  4. package/dist/capabilities-default.mjs +1 -1
  5. package/dist/{constants-56jf_4ls.d.mts → constants-D-5m_KGB.d.mts} +7 -7
  6. package/dist/constants-D-5m_KGB.d.mts.map +1 -0
  7. package/dist/{controller-proxy-ClsY4lFI.mjs → controller-proxy-0WJZUZYx.mjs} +2 -2
  8. package/dist/{controller-proxy-ClsY4lFI.mjs.map → controller-proxy-0WJZUZYx.mjs.map} +1 -1
  9. package/dist/{controller-proxy-2VH0b_5E.d.mts → controller-proxy-Hkuub9dt.d.mts} +2 -2
  10. package/dist/{controller-proxy-2VH0b_5E.d.mts.map → controller-proxy-Hkuub9dt.d.mts.map} +1 -1
  11. package/dist/{engine-DQrCO_ur.mjs → engine-0AJrv0hI.mjs} +543 -76
  12. package/dist/engine-0AJrv0hI.mjs.map +1 -0
  13. package/dist/engine.d.mts +2 -2
  14. package/dist/engine.mjs +3 -3
  15. package/dist/{index-B_x2IYi7.d.mts → index-BcyfBeDj.d.mts} +3 -12
  16. package/dist/index-BcyfBeDj.d.mts.map +1 -0
  17. package/dist/{index-C_x686M7.d.mts → index-CneD8ibB.d.mts} +3 -3
  18. package/dist/{index-C_x686M7.d.mts.map → index-CneD8ibB.d.mts.map} +1 -1
  19. package/dist/{index-DZ6KBwVR.d.mts → index-JXH-t2EJ.d.mts} +45 -14
  20. package/dist/index-JXH-t2EJ.d.mts.map +1 -0
  21. package/dist/{index-DVxVyhAu.d.mts → index-MEOL9_Gr.d.mts} +4 -12
  22. package/dist/{index-DVxVyhAu.d.mts.map → index-MEOL9_Gr.d.mts.map} +1 -1
  23. package/dist/{memory-thread-repository-Cm1BJyRw.mjs → memory-thread-repository-1ocPOda8.mjs} +17 -17
  24. package/dist/memory-thread-repository-1ocPOda8.mjs.map +1 -0
  25. package/dist/{protocol-7EEgcYja.mjs → protocol-DpCYmCjr.mjs} +1 -8
  26. package/dist/{protocol-7EEgcYja.mjs.map → protocol-DpCYmCjr.mjs.map} +1 -1
  27. package/dist/protocol.d.mts +1 -1
  28. package/dist/protocol.mjs +1 -1
  29. package/dist/{react-B_5smfiK.mjs → react-DXK5zWNo.mjs} +3 -3
  30. package/dist/{react-B_5smfiK.mjs.map → react-DXK5zWNo.mjs.map} +1 -1
  31. package/dist/react-integration.d.mts +1 -1
  32. package/dist/react-integration.mjs +1 -1
  33. package/dist/react.d.mts +2 -2
  34. package/dist/react.mjs +2 -2
  35. package/dist/references/assistant-operations.md +9 -14
  36. package/dist/references/localization.md +2 -0
  37. package/dist/references/source-material-surveys.md +1 -1
  38. package/dist/{request-context-BDBw4-Gw.mjs → request-context-BBzU3VdG.mjs} +17 -10
  39. package/dist/request-context-BBzU3VdG.mjs.map +1 -0
  40. package/dist/server-agent.d.mts +4 -12
  41. package/dist/server-agent.mjs +13 -13
  42. package/dist/server-agent.mjs.map +1 -1
  43. package/dist/server-runtime.d.mts +4 -4
  44. package/dist/server-runtime.mjs +2 -2
  45. package/dist/server-tasks.d.mts +1 -1
  46. package/dist/server-tasks.mjs +1 -1
  47. package/dist/server-tools.d.mts +1 -1
  48. package/dist/server-tools.mjs +1 -1
  49. package/dist/server.d.mts +6 -6
  50. package/dist/server.d.mts.map +1 -1
  51. package/dist/server.mjs +2 -2
  52. package/dist/{tasks-COQnhSeT.d.mts → tasks-DcfQNJwO.d.mts} +3 -3
  53. package/dist/{tasks-COQnhSeT.d.mts.map → tasks-DcfQNJwO.d.mts.map} +1 -1
  54. package/dist/thread-documents-BlxNjc7S.d.mts.map +1 -1
  55. package/dist/{thread-handlers-l79JBl5m.d.mts → thread-handlers-uddXwQjp.d.mts} +3 -3
  56. package/dist/{thread-handlers-l79JBl5m.d.mts.map → thread-handlers-uddXwQjp.d.mts.map} +1 -1
  57. package/dist/{tools-DDXaJ8Za.mjs → tools-wgLen05n.mjs} +97 -103
  58. package/dist/tools-wgLen05n.mjs.map +1 -0
  59. package/dist/ui.css +1 -1
  60. package/dist/ui.d.mts +3 -3
  61. package/dist/ui.d.mts.map +1 -1
  62. package/dist/ui.mjs +36 -6
  63. package/dist/ui.mjs.map +1 -1
  64. package/package.json +4 -4
  65. package/dist/constants-56jf_4ls.d.mts.map +0 -1
  66. package/dist/engine-DQrCO_ur.mjs.map +0 -1
  67. package/dist/index-B_x2IYi7.d.mts.map +0 -1
  68. package/dist/index-DZ6KBwVR.d.mts.map +0 -1
  69. package/dist/memory-thread-repository-Cm1BJyRw.mjs.map +0 -1
  70. package/dist/request-context-BDBw4-Gw.mjs.map +0 -1
  71. package/dist/tools-DDXaJ8Za.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
- import { G as assistantItemTypeManifestSchema, J as SURVEY_OPERATION_KINDS, _t as surveyOperationSchema, u as surveyChangeSetSchema, vt as surveyOperationSchemasByKind } from "./protocol-7EEgcYja.mjs";
2
- import { a as getDeterministicCreateItemTypes, o as inspectAssistantItem, s as resolveAssistantCapabilityReference } from "./capabilities-DZ0a_o2y.mjs";
3
- import { A as getRawSurvey, B as resolveItemSummary, C as clampLimit, F as mapKeyIndexById, H as summarizeFocusItemNode, I as mapOutlineById, J as translationEntrySchema, K as summarizeOutlineNode, L as mapRawItemsById, M as itemConfigDetailsSchema, N as itemMatchesQuery, P as itemSummarySchema, R as normalizeText, U as summarizeItemById, V as sliceWithCursor, W as summarizeKeyIndexEntries, Y as unique, _ as buildConfigSearchText, a as SURVEY_CHANGE_CONTINUATION_MODES, b as buildItemTranslationEntries, d as normalizeSurveyChangeOperations, f as buildExpressionMutationPatches, i as SURVEY_CHANGE_CONTINUATION_DESCRIPTION, j as getRawSurveyItems, k as getKeyIndexEntries, l as createSurveyChangeSet, p as expressionMutationSchema, q as summarizeRawItem, t as auditSurvey, v as buildEmbeddedFormDetails, w as findDirectChildrenByFullKey, y as buildItemConfigDetails } from "./engine-DQrCO_ur.mjs";
4
- import { a as requireSurveyAssistantTurnState } from "./request-context-BDBw4-Gw.mjs";
1
+ import { G as assistantItemTypeManifestSchema, J as SURVEY_OPERATION_KINDS, _t as surveyOperationSchema, u as surveyChangeSetSchema, vt as surveyOperationSchemasByKind } from "./protocol-DpCYmCjr.mjs";
2
+ import { a as getDeterministicCreateItemTypes, o as inspectAssistantItem, s as resolveAssistantCapabilityReference } from "./capabilities-DKbNCnBf.mjs";
3
+ import { A as getRawSurvey, B as resolveItemSummary, C as clampLimit, F as mapKeyIndexById, H as summarizeFocusItemNode, I as mapOutlineById, J as translationEntrySchema, K as summarizeOutlineNode, L as mapRawItemsById, M as itemConfigDetailsSchema, N as itemMatchesQuery, P as itemSummarySchema, R as normalizeText, U as summarizeItemById, V as sliceWithCursor, W as summarizeKeyIndexEntries, Y as unique, _ as buildConfigSearchText, b as buildItemTranslationEntries, d as normalizeSurveyChangeOperations, f as buildExpressionMutationPatches, j as getRawSurveyItems, k as getKeyIndexEntries, l as createSurveyChangeSet, p as expressionMutationSchema, q as summarizeRawItem, t as auditSurvey, v as buildEmbeddedFormDetails, w as findDirectChildrenByFullKey, y as buildItemConfigDetails } from "./engine-0AJrv0hI.mjs";
4
+ import { o as requireSurveyAssistantTurnState, r as getSurveyAssistantEngineContext } from "./request-context-BBzU3VdG.mjs";
5
5
  import { n as surveyAuthoringReferenceTopics, r as surveyAuthoringReferences, t as getBuiltInItemTypeAuthoringReference } from "./authoring-references-DYuOAdVx.mjs";
6
6
  import { z } from "zod";
7
7
  import { JsonPatchError, SURVEY_EDITOR_ITEM_COLORS, encodeJsonPointerSegment, getAssetUsagesFromContent, getValueAtJsonPointer } from "@case-framework/survey-core";
@@ -59,7 +59,7 @@ const formatItemCapabilityProfile = (manifest, kind) => {
59
59
  kind === "create-item" && requiredSurfaces.length > 0 ? `Required on creation: ${requiredSurfaces.map((surface) => surface.key).join(", ")}.` : void 0,
60
60
  manifest.referenceTopics?.length ? `Load a dedicated reference only when needed: ${manifest.referenceTopics.join(", ")}.` : void 0
61
61
  ].filter((line) => Boolean(line));
62
- if (kind === "create-item") lines.push("", "Creation scope: put the complete initial item structure, configuration, and visible translations in this create-item operation. Operations that edit an item by itemId target items already present in the immutable live snapshot; do not create an empty item and then target its new id with option or field editing operations in the same response.");
62
+ 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.");
63
63
  return lines.join("\n");
64
64
  };
65
65
  const formatSurveyOperationContractReference = (kind, options = {}) => {
@@ -138,7 +138,7 @@ const surveyAuthoringReferenceTool = createTool({
138
138
  } },
139
139
  execute: async (inputData, context) => {
140
140
  const contractDetail = inputData.detail ?? "compact";
141
- const engineContext = context?.requestContext?.get("engineContext");
141
+ const engineContext = getSurveyAssistantEngineContext(context?.requestContext);
142
142
  const itemCapability = inputData.itemType ? engineContext?.trustedCapabilities?.byItemType.get(inputData.itemType)?.manifest ?? engineContext?.capabilities.itemTypes.find((entry) => entry.itemType === inputData.itemType) : void 0;
143
143
  if (inputData.operationKind && inputData.topic !== "assistant-operations") return {
144
144
  ok: false,
@@ -229,23 +229,20 @@ const OPERATIONS_DESCRIPTION = [
229
229
  "Any translations owned by an operation must be inside that same operation object. Never put translations beside the survey_change operations array.",
230
230
  "Create items with { kind: \"create-item\", target: { parentItemId, index? }, item: { id?, key, itemType, config }, translations? }. Put respondent content in top-level translations and item settings in item.config.",
231
231
  "Use update-item-translation with itemId, locale, contentKey, and plainText for ordinary text or content for a complete structured value. Item titles and option/field labels are translations, not item labels.",
232
- "Item-editing operations target items already present in the immutable live snapshot. For a new item, put its complete initial options, fields, config, translations, and embedded expressions directly in create-item; do not create it empty and target its new id later in the response.",
232
+ "Use remove-survey-locale with locale to remove an existing survey language; the final remaining locale cannot be removed.",
233
+ "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.",
233
234
  "For an existing choice item, use add-choice-option with itemId, optional locale/index, and option: { id?, key?, label }; label is a plain string and creates its translation.",
234
235
  "Every reorder replaces the complete inspected list: reorder-choice-options uses optionIds, reorder-form-field-groups uses groupIds, and reorder-items uses itemIds for every direct child. Include every existing entry exactly once in desired order.",
235
236
  `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.`}`,
236
237
  "Use patch-item-config with itemId and plural patches whose JSON Pointer paths are relative to that item config root.",
237
- "Use update-placed-image only for an image already present in the immutable snapshot. For a new item, embed the complete image block directly in create-item translations.",
238
+ "Use update-placed-image only for an image placement that already exists in the live survey or validated turn draft.",
238
239
  "When deleting referenced images and assets, include every reference-removal operation and remove-image-asset last in the same operations array.",
239
240
  "Raw fallback is one { kind: \"survey-json-patch\", patches } operation containing all related survey-rooted edits; it must be the only operation in the call.",
240
- "Keep proposals bounded; split oversized work into independent complete change calls."
241
+ "Keep each delta bounded; successful chunks accumulate automatically in the turn draft."
241
242
  ].join(" ");
242
243
  const inputSchema$9 = z.object({
243
- intent: z.string().min(1).max(500).describe("The concrete survey change requested by the user."),
244
244
  summary: z.string().min(1).max(500).describe("Short user-facing summary of the intended survey change."),
245
- operations: z.array(z.unknown()).min(1).max(200).describe(OPERATIONS_DESCRIPTION),
246
- continuationMode: z.enum(SURVEY_CHANGE_CONTINUATION_MODES).nullish().transform((value) => value ?? void 0).describe(`${SURVEY_CHANGE_CONTINUATION_DESCRIPTION} Omit this field instead of sending null.`),
247
- inspectedContext: z.array(z.string().max(240)).max(20).nullish().transform((value) => value ?? void 0).describe("Optional item ids, paths, or references inspected before proposing this change."),
248
- notes: z.string().max(1e3).nullish().transform((value) => value ?? void 0).describe("Optional implementation notes for validation/debugging; not shown as persisted survey content.")
245
+ operations: z.array(z.unknown()).min(1).max(200).describe(OPERATIONS_DESCRIPTION)
249
246
  }).strict();
250
247
  const rejectedOperationParseEvent = ({ issueText, summary, turnId }) => ({
251
248
  protocolVersion: 1,
@@ -301,29 +298,22 @@ const describeOperationTarget = (operation) => {
301
298
  }
302
299
  };
303
300
  const summarizeValidation = (event) => {
304
- const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} survey change: ${event.summary}`, `Change id: ${event.id}`];
305
- if (event.operations.length > 0) lines.push(`Operations: ${event.operations.map((operation) => operation.kind).join(", ")}`);
301
+ const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} survey change: ${event.summary}`];
306
302
  if (event.status === "rejected") {
307
- lines.push("Apply status: blocked; this candidate is not authorized.");
308
- if (event.recovery?.requiredContinuationMode) lines.push(`Required recovery: resubmit the complete intended proposal with continuationMode "${event.recovery.requiredContinuationMode}". No survey change from this turn can be authorized until that replacement validates.`);
309
- else if (event.recovery) lines.push(`Required recovery: resubmit the complete proposal with an explicit continuationMode (${event.recovery.allowedContinuationModes.join(" or ")}).`);
303
+ lines.push("Draft unchanged. Fix this same rejected target in another call; the previously validated draft is retained. An unrelated valid edit does not repair this rejection.");
310
304
  if (event.operations.length > 0) {
311
305
  const checklist = event.operations.slice(0, 24).map(describeOperationTarget);
312
306
  lines.push(`Unapplied target sample (${checklist.length} of ${event.operations.length}):\n${checklist.map((target) => `- ${target}`).join("\n")}`);
313
307
  if (event.operations.length > checklist.length) lines.push(`The sample omits ${event.operations.length - checklist.length} additional operation target(s). The original tool input is authoritative; preserve its complete requested scope when retrying.`);
314
308
  }
315
309
  }
316
- if (event.status === "validated") {
317
- lines.push("Lifecycle: this candidate is registered, not live. Do not target its new ids again in this response; successful turn completion authorizes the collector's final candidate ids for browser apply.");
318
- if (event.supersedesChangeIds?.length) lines.push(`Queue: replaces ${event.supersedesChangeIds.length} earlier survey_change proposal(s) from this response.`);
319
- }
320
- if (event.supersedesChangeIds?.length && event.status === "rejected") lines.push(`Queue: discarded ${event.supersedesChangeIds.length} earlier candidate proposal(s); their work is not retained or authorized. Include that discarded work in the complete replacement before claiming the request is complete.`);
310
+ if (event.status === "validated") 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.`);
321
311
  if (event.validation.issues.length > 0) {
322
312
  lines.push("Issues:");
323
313
  for (const issue of event.validation.issues) lines.push(`- ${issue.code}: ${issue.message}`);
324
314
  const issueCodes = new Set(event.validation.issues.map((issue) => issue.code));
325
- if (issueCodes.has("missing-create-content") || issueCodes.has("missing-field-groups-wrapper") || issueCodes.has("missing-choice-options") || issueCodes.has("missing-choice-option-labels")) lines.push("Recovery hint: load the assistant-operations reference, then retry the complete create-item proposal with continuationMode \"replace\". Put visible text in top-level translations[] beside item, config inside item.config, choice options in item.config.options, and form fields inside item.config.fieldGroups[].fields. Every choice option id needs a matching top-level translation whose contentKey is options.<optionId>.label.");
326
- if (issueCodes.has("too-many-operations")) lines.push("Recovery hint: keep the same inspected survey context and immediately split these edits into smaller survey_change calls. Use continuationMode \"replace\" on the first repaired chunk, then \"append\" on every later independent chunk. Do not claim that a new snapshot is needed unless a tool explicitly reported a missing snapshot.");
315
+ if (issueCodes.has("missing-create-content") || issueCodes.has("missing-field-groups-wrapper") || issueCodes.has("missing-choice-options") || issueCodes.has("missing-choice-option-labels")) lines.push("Recovery hint: load the assistant-operations reference, then retry only the rejected delta. Put visible text in top-level translations[] beside item, config inside item.config, choice options in item.config.options, and form fields inside item.config.fieldGroups[].fields. Every choice option id needs a matching top-level translation whose contentKey is options.<optionId>.label.");
316
+ if (issueCodes.has("too-many-operations")) lines.push("Recovery hint: keep the same inspected survey context and split this rejected delta into smaller survey_change calls. Each successful call extends the same turn draft.");
327
317
  }
328
318
  if (event.validation.warnings.length > 0) {
329
319
  lines.push("Warnings:");
@@ -333,7 +323,7 @@ const summarizeValidation = (event) => {
333
323
  };
334
324
  const surveyChangeTool = createTool({
335
325
  id: TOOL_ID$9,
336
- description: "Validate and queue one final, bounded, undoable survey change. This is not a dry run and does not mutate the live snapshot during the response. The live snapshot is immutable: item-editing operations target existing items, while each create-item must contain that new item's complete initial config, content, options, fields, and embedded expressions. Multiple create-item operations may be ordered by dependency in one proposal. Omit continuationMode only on the first mutation call in a turn; every later mutation call must use \"append\" for independent additional work or \"replace\" for a correction. Every operation uses \"kind\".",
326
+ 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\".",
337
327
  inputSchema: inputSchema$9,
338
328
  outputSchema: surveyChangeSetSchema,
339
329
  mcp: { annotations: {
@@ -344,23 +334,20 @@ const surveyChangeTool = createTool({
344
334
  openWorldHint: false
345
335
  } },
346
336
  execute: async (inputData, context) => {
347
- const { changeCollector, turnId } = requireSurveyAssistantTurnState(context?.requestContext);
348
- const snapshot = context?.requestContext?.get("engineContext");
337
+ const { turnDraft, turnId } = requireSurveyAssistantTurnState(context?.requestContext);
338
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
349
339
  const defaultLocale = snapshot?.ui.selectedLocale ?? snapshot?.inspection.locales[0] ?? "en";
350
- const continuationMode = inputData.continuationMode ?? void 0;
351
340
  const operationsParse = z.array(surveyOperationSchema).safeParse(normalizeSurveyChangeOperations(inputData.operations, defaultLocale));
352
- if (!operationsParse.success) return changeCollector.register(rejectedOperationParseEvent({
341
+ if (!operationsParse.success) return turnDraft.reject(rejectedOperationParseEvent({
353
342
  summary: inputData.summary,
354
343
  turnId,
355
344
  issueText: operationsParse.error.issues.slice(0, 8).map((issue) => `${issue.path.join(".") || "operation"}: ${issue.message}`).join("; ")
356
- }), { continuationMode });
357
- const event = createSurveyChangeSet({
345
+ }));
346
+ return turnDraft.stage({
358
347
  operations: operationsParse.data,
359
348
  snapshot,
360
- summary: inputData.summary,
361
- turnId
349
+ summary: inputData.summary
362
350
  });
363
- return changeCollector.register(event, { continuationMode });
364
351
  },
365
352
  toModelOutput: (value) => toTextModelOutput(value, summarizeValidation)
366
353
  });
@@ -373,32 +360,20 @@ const contextActionSchema = z.object({
373
360
  itemIds: z.array(z.string().min(1).max(240)).max(12).optional(),
374
361
  includeRawLocations: z.boolean().optional()
375
362
  });
376
- const continuationModeSchema = z.enum(SURVEY_CHANGE_CONTINUATION_MODES).optional().describe(SURVEY_CHANGE_CONTINUATION_DESCRIPTION);
377
363
  const prepareActionSchema = z.object({
378
364
  action: z.literal("prepare"),
379
- intent: z.string().min(1).max(500),
380
365
  summary: z.string().min(1).max(500),
381
- mutations: z.array(expressionMutationSchema).min(1).max(40),
382
- continuationMode: continuationModeSchema,
383
- inspectedContext: z.array(z.string().max(240)).max(20).optional()
366
+ mutations: z.array(expressionMutationSchema).min(1).max(40)
384
367
  });
385
368
  const inputSchema$8 = z.object({
386
369
  action: z.enum(["context", "prepare"]).describe("Use context to inspect expression refs; use prepare to create expression edits."),
387
370
  reason: z.string().max(500).optional(),
388
371
  itemIds: z.array(z.string().min(1).max(240)).max(12).optional().describe("For context: optional item ids/keys to focus existing expression locations."),
389
372
  includeRawLocations: z.boolean().optional().describe("For context: include existing expression locations by raw item."),
390
- intent: z.string().min(1).max(500).optional().describe("For prepare: requested expression edit."),
391
373
  summary: z.string().min(1).max(500).optional().describe("For prepare: user-facing summary."),
392
- mutations: z.array(z.record(z.string(), z.unknown())).min(1).max(40).optional().describe("For prepare: expression mutation objects using kebab-case kind names. Item visibility shape: { kind: \"set-item-display-condition\", itemId: \"<existing-item-id>\", expression: { type: \"function\", functionName: \"eq\", arguments: [...] } }. expression is always CASE expression JSON, never a string or expression-language snippet. Other supported kinds set/clear component display/disabled conditions, validation expressions, prefills, and template values."),
393
- continuationMode: continuationModeSchema,
394
- inspectedContext: z.array(z.string().max(240)).max(20).optional()
374
+ mutations: z.array(z.record(z.string(), z.unknown())).min(1).max(40).optional().describe("For prepare: expression mutation objects using kebab-case kind names. Item visibility shape: { kind: \"set-item-display-condition\", itemId: \"<existing-item-id>\", expression: { type: \"function\", functionName: \"eq\", arguments: [...] } }. expression is always CASE expression JSON, never a string or expression-language snippet. Other supported kinds set/clear component display/disabled conditions, validation expressions, prefills, and template values.")
395
375
  }).strict().superRefine((value, ctx) => {
396
376
  if (value.action !== "prepare") return;
397
- if (!value.intent) ctx.addIssue({
398
- code: z.ZodIssueCode.custom,
399
- path: ["intent"],
400
- message: "Required for prepare."
401
- });
402
377
  if (!value.summary) ctx.addIssue({
403
378
  code: z.ZodIssueCode.custom,
404
379
  path: ["summary"],
@@ -432,6 +407,22 @@ const createIssue = ({ severity = "error", ...input }) => ({
432
407
  severity,
433
408
  ...input
434
409
  });
410
+ const expressionMutationTarget = (mutation) => {
411
+ switch (mutation.kind) {
412
+ case "set-item-display-condition":
413
+ case "clear-item-display-condition": return `expression:item:${mutation.itemId}:display`;
414
+ case "set-component-display-condition":
415
+ case "clear-component-display-condition": return `expression:item:${mutation.itemId}:component:${mutation.componentId}:display`;
416
+ case "set-component-disabled-condition":
417
+ case "clear-component-disabled-condition": return `expression:item:${mutation.itemId}:component:${mutation.componentId}:disabled`;
418
+ case "set-validation-expression":
419
+ case "clear-validation-expression": return `expression:item:${mutation.itemId}:validation:${mutation.validationKey}`;
420
+ case "set-prefill": return `expression:item:${mutation.itemId}:prefill:${mutation.prefill.id}`;
421
+ case "clear-prefill": return `expression:item:${mutation.itemId}:prefill:${mutation.prefillId}`;
422
+ case "set-template-value":
423
+ case "clear-template-value": return `expression:template:${mutation.templateKey}`;
424
+ }
425
+ };
435
426
  const buildResponseSlotTypes = (snapshot) => snapshot.responseSlotTypes;
436
427
  const rejectedEvent = ({ issues, summary, turnId, warnings = [] }) => ({
437
428
  protocolVersion: 1,
@@ -449,14 +440,9 @@ const rejectedEvent = ({ issues, summary, turnId, warnings = [] }) => ({
449
440
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
450
441
  });
451
442
  const summarizeDraftEvent = (event) => {
452
- const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} expression change: ${event.summary}`, `Change id: ${event.id}`];
453
- if (event.operations.length > 0) lines.push(`Operations: ${event.operations.map((operation) => operation.kind).join(", ")}`);
454
- if (event.status === "rejected") {
455
- lines.push("Apply status: blocked; this candidate is not authorized.");
456
- if (event.recovery?.requiredContinuationMode) lines.push(`Required recovery: resubmit the complete intended proposal with continuationMode "${event.recovery.requiredContinuationMode}". No survey change from this turn can be authorized until that replacement validates.`);
457
- else if (event.recovery) lines.push(`Required recovery: resubmit the complete proposal with an explicit continuationMode (${event.recovery.allowedContinuationModes.join(" or ")}).`);
458
- }
459
- if (event.supersedesChangeIds?.length) lines.push(event.status === "validated" ? `Queue: replaces ${event.supersedesChangeIds.length} earlier candidate proposal(s) from this response.` : `Queue: discarded ${event.supersedesChangeIds.length} earlier candidate proposal(s); their work is not retained or authorized.`);
443
+ const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} expression change: ${event.summary}`];
444
+ if (event.status === "validated") lines.push(`Draft updated (${event.operations.length} total operation${event.operations.length === 1 ? "" : "s"}). The expression is immediately visible to later tools in this turn.`);
445
+ if (event.status === "rejected") lines.push("Draft unchanged. Fix this same expression target; the previously validated draft is retained. An unrelated valid edit does not repair this rejection.");
460
446
  if (event.validation.issues.length > 0) {
461
447
  lines.push("Issues:");
462
448
  for (const issue of event.validation.issues) lines.push(`- ${issue.code}: ${issue.message}`);
@@ -518,7 +504,7 @@ const buildContextOutput = (snapshot, input) => {
518
504
  };
519
505
  const surveyExpressionTool = createTool({
520
506
  id: TOOL_ID$8,
521
- description: "Inspect response refs or queue expression edits for CASE survey items already present in the live snapshot. Covers display and disabled conditions, validations, prefills, and template values. Newly proposed items are not visible until the response ends; embed their expressions in the same survey_change call that creates them. For prepare, omit continuationMode only on the first mutation call in a turn; every later mutation call must use \"append\" or \"replace\".",
507
+ 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.",
522
508
  inputSchema: inputSchema$8,
523
509
  outputSchema: outputSchema$7,
524
510
  mcp: { annotations: {
@@ -531,7 +517,7 @@ const surveyExpressionTool = createTool({
531
517
  execute: async (inputData, context) => {
532
518
  const input = inputData;
533
519
  const requestContext = context?.requestContext;
534
- const snapshot = context?.requestContext?.get("engineContext");
520
+ const snapshot = getSurveyAssistantEngineContext(requestContext);
535
521
  if (!snapshot) {
536
522
  if (input.action === "context") return {
537
523
  ok: false,
@@ -540,24 +526,24 @@ const surveyExpressionTool = createTool({
540
526
  listOrdering: { responseSlots: "Set-like; unavailable because no live snapshot was provided." },
541
527
  responseSlots: []
542
528
  };
543
- const { changeCollector, turnId } = requireSurveyAssistantTurnState(requestContext);
544
- return changeCollector.register(rejectedEvent({
529
+ const { turnDraft, turnId } = requireSurveyAssistantTurnState(requestContext);
530
+ return turnDraft.reject(rejectedEvent({
545
531
  summary: input.summary ?? "Expression change",
546
532
  turnId,
547
533
  issues: [createIssue({
548
534
  code: "missing-snapshot",
549
535
  message: "No live editor snapshot is available for expression work."
550
536
  })]
551
- }), { continuationMode: input.continuationMode });
537
+ }));
552
538
  }
553
539
  if (input.action === "context") {
554
540
  const contextParse = contextActionSchema.safeParse(inputData);
555
541
  return buildContextOutput(snapshot, contextParse.success ? contextParse.data : input);
556
542
  }
557
- const { changeCollector, turnId } = requireSurveyAssistantTurnState(requestContext);
558
- const register = (event) => changeCollector.register(event, { continuationMode: input.continuationMode });
543
+ const { turnDraft, turnId } = requireSurveyAssistantTurnState(requestContext);
544
+ const reject = (event, addressedTargets = []) => turnDraft.reject(event, addressedTargets);
559
545
  const prepareParse = prepareActionSchema.safeParse(inputData);
560
- if (!prepareParse.success) return register(rejectedEvent({
546
+ if (!prepareParse.success) return reject(rejectedEvent({
561
547
  summary: input.summary ?? "Expression change",
562
548
  turnId,
563
549
  issues: [createIssue({
@@ -566,6 +552,7 @@ const surveyExpressionTool = createTool({
566
552
  })]
567
553
  }));
568
554
  const prepareInput = prepareParse.data;
555
+ const addressedTargets = [...new Set(prepareInput.mutations.map(expressionMutationTarget))];
569
556
  const issues = [];
570
557
  const patches = [];
571
558
  const responseSlotTypes = buildResponseSlotTypes(snapshot);
@@ -581,13 +568,13 @@ const surveyExpressionTool = createTool({
581
568
  });
582
569
  const errors = issues.filter((issue) => issue.severity === "error");
583
570
  const warnings = issues.filter((issue) => issue.severity === "warning");
584
- if (errors.length > 0) return register(rejectedEvent({
571
+ if (errors.length > 0) return reject(rejectedEvent({
585
572
  summary: prepareInput.summary,
586
573
  turnId,
587
574
  issues: errors,
588
575
  warnings
589
- }));
590
- if (patches.length === 0) return register(rejectedEvent({
576
+ }), addressedTargets);
577
+ if (patches.length === 0) return reject(rejectedEvent({
591
578
  summary: prepareInput.summary,
592
579
  turnId,
593
580
  issues: [createIssue({
@@ -595,23 +582,24 @@ const surveyExpressionTool = createTool({
595
582
  message: "No expression patches were produced. Check whether the requested locations already match the target state."
596
583
  })],
597
584
  warnings
598
- }));
599
- const event = createSurveyChangeSet({
600
- operations: [{
601
- kind: "survey-json-patch",
602
- patches
603
- }],
585
+ }), addressedTargets);
586
+ const operation = {
587
+ kind: "survey-json-patch",
588
+ patches
589
+ };
590
+ const event = turnDraft.stage({
591
+ addressedTargets,
592
+ operations: [operation],
604
593
  snapshot,
605
- summary: prepareInput.summary,
606
- turnId
594
+ summary: prepareInput.summary
607
595
  });
608
- return register({
596
+ return {
609
597
  ...event,
610
598
  validation: {
611
599
  ...event.validation,
612
600
  warnings: [...event.validation.warnings, ...warnings]
613
601
  }
614
- });
602
+ };
615
603
  },
616
604
  toModelOutput: (value) => toTextModelOutput(value, (output) => "action" in output && output.action === "context" ? output.modelSummary : summarizeDraftEvent(output))
617
605
  });
@@ -938,7 +926,7 @@ const surveyGetCurrentContextTool = createTool({
938
926
  limit: inputData.limit ?? 12,
939
927
  outlineMode: inputData.outlineMode ?? "full"
940
928
  };
941
- const snapshot = context?.requestContext?.get("engineContext");
929
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
942
930
  if (!snapshot) return {
943
931
  ok: false,
944
932
  reason: "No live editor snapshot was provided with this assistant turn.",
@@ -1079,7 +1067,7 @@ const surveyGetItemDetailsTool = createTool({
1079
1067
  includeItemConfig: inputData.includeItemConfig ?? true,
1080
1068
  childrenLimit: inputData.childrenLimit ?? DEFAULT_CHILDREN_LIMIT
1081
1069
  };
1082
- const snapshot = context?.requestContext?.get("engineContext");
1070
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
1083
1071
  if (!snapshot) return {
1084
1072
  ok: false,
1085
1073
  reason: "No live editor snapshot was provided with this assistant turn."
@@ -1345,7 +1333,7 @@ const surveyGetRawSurveyTool = createTool({
1345
1333
  ...inputData,
1346
1334
  valueCharacterLimit: inputData.valueCharacterLimit ?? DEFAULT_VALUE_CHARACTER_LIMIT
1347
1335
  };
1348
- const snapshot = context?.requestContext?.get("engineContext");
1336
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
1349
1337
  if (!snapshot) return {
1350
1338
  ok: false,
1351
1339
  reason: "No live editor snapshot was provided with this assistant turn."
@@ -1569,7 +1557,7 @@ const surveySearchItemsTool = createTool({
1569
1557
  ...inputData,
1570
1558
  limit: inputData.limit ?? 12
1571
1559
  };
1572
- const snapshot = context?.requestContext?.get("engineContext");
1560
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
1573
1561
  if (!snapshot) return {
1574
1562
  ok: false,
1575
1563
  reason: "No live editor snapshot was provided with this assistant turn.",
@@ -1606,23 +1594,24 @@ const outlineModeSchema = z.enum([
1606
1594
  "children"
1607
1595
  ]);
1608
1596
  const optionalInput = (schema) => z.preprocess((value) => value === null ? void 0 : value, schema.optional());
1597
+ const optionalArrayInput = (schema) => z.preprocess((value) => value === null || Array.isArray(value) && value.length === 0 ? void 0 : value, schema.optional());
1609
1598
  const optionalNonBlankString = (maxLength) => z.preprocess((value) => value === null || typeof value === "string" && value.trim().length === 0 ? void 0 : value, z.string().min(1).max(maxLength).optional());
1610
1599
  const inputSchema$3 = z.object({
1611
1600
  action: inspectActionSchema.describe("The survey information to inspect."),
1612
1601
  reason: optionalInput(z.string().max(500)).describe("Short reason this inspection is needed."),
1613
- scopes: optionalInput(z.array(z.enum([
1602
+ scopes: optionalArrayInput(z.array(z.enum([
1614
1603
  "summary",
1615
1604
  "selected-item",
1616
1605
  "outline",
1617
1606
  "response-slots",
1618
1607
  "ui-context",
1619
1608
  "history"
1620
- ])).min(1).max(6)).describe("For current-context: context sections to inspect."),
1609
+ ])).min(1).max(6)).describe("For current-context: context sections to inspect. Empty means omitted."),
1621
1610
  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 key/fullKey/label filter."),
1622
1611
  itemRef: optionalNonBlankString(240).describe("For current-context outline, item-details, or raw-json: item id, key, fullKey, visible label, or \"selected\"."),
1623
1612
  cursor: optionalNonBlankString(240).describe("For current-context/search-items pagination: numeric cursor from a previous result."),
1624
1613
  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."),
1625
- paths: optionalInput(z.array(z.string().min(1).max(500)).min(1).max(20)).describe("For raw-json: exact JSON Pointer paths to inspect."),
1614
+ paths: optionalArrayInput(z.array(z.string().min(1).max(500)).min(1).max(20)).describe("For raw-json: exact JSON Pointer paths to inspect. Empty means omitted."),
1626
1615
  includeChildren: optionalInput(z.boolean()).describe("For item-details: include direct survey-tree children."),
1627
1616
  includeItemConfig: optionalInput(z.boolean()).describe("For item-details: include choice/form/internal item config details."),
1628
1617
  limit: optionalInput(z.number().int().min(1).max(50)).describe("Maximum list rows to return.")
@@ -1728,7 +1717,7 @@ const listAssets = (input, snapshot) => {
1728
1717
  };
1729
1718
  const surveyInspectTool = createTool({
1730
1719
  id: TOOL_ID$3,
1731
- description: "Inspect current survey state through one compact interface: current context, item search, item details, image assets with explicit asset IDs and usages, or focused raw JSON. Use this before making changes when the answer depends on the live editor state.",
1720
+ description: "Inspect the current request-scoped survey draft through one compact interface: current context, item search, item details, 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.",
1732
1721
  inputSchema: inputSchema$3,
1733
1722
  outputSchema: outputSchema$2,
1734
1723
  mcp: { annotations: {
@@ -1740,12 +1729,12 @@ const surveyInspectTool = createTool({
1740
1729
  } },
1741
1730
  execute: async (inputData, context) => {
1742
1731
  const input = inputData;
1743
- const snapshot = context?.requestContext?.get("engineContext");
1732
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
1744
1733
  if (!snapshot) return {
1745
1734
  ok: false,
1746
1735
  action: input.action,
1747
- reason: "No live editor snapshot was provided with this assistant turn.",
1748
- modelSummary: "No live editor snapshot is available for this turn."
1736
+ reason: "No editor snapshot was provided with this assistant turn.",
1737
+ modelSummary: "No survey draft is available for this turn."
1749
1738
  };
1750
1739
  if (input.action === "current-context") {
1751
1740
  const result = unwrapToolOutput(await runTool(surveyGetCurrentContextTool, {
@@ -1886,7 +1875,7 @@ const surveyAuditTool = createTool({
1886
1875
  openWorldHint: false
1887
1876
  } },
1888
1877
  execute: async (input, context) => {
1889
- const snapshot = context?.requestContext?.get("engineContext");
1878
+ const snapshot = getSurveyAssistantEngineContext(context?.requestContext);
1890
1879
  if (!snapshot) return {
1891
1880
  ok: false,
1892
1881
  reason: "No live editor snapshot is available for this turn."
@@ -1912,7 +1901,6 @@ const inputSchema = z.object({
1912
1901
  name: z.string().max(500).nullable().optional(),
1913
1902
  filename: z.string().max(500).nullable().optional(),
1914
1903
  alt: z.string().max(2e3).nullable().optional(),
1915
- continuationMode: z.enum(SURVEY_CHANGE_CONTINUATION_MODES).optional().describe("Required after any earlier mutation candidate in the turn. Use \"append\" to keep earlier candidates, including an imported asset; use \"replace\" only when resubmitting the complete intended proposal."),
1916
1904
  summary: z.string().min(1).max(500)
1917
1905
  }).strict();
1918
1906
  const slug = (value) => value.toLowerCase().replace(/\.[^.]+$/, "").replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100) || "image";
@@ -1944,14 +1932,14 @@ const describeAssetOperation = (operation) => {
1944
1932
  }
1945
1933
  };
1946
1934
  const summarizeAssetChange = (event) => {
1947
- const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} survey asset change: ${event.summary}`, `Change id: ${event.id}`];
1948
- if (event.operations.length > 0) lines.push(`Operations: ${event.operations.map(describeAssetOperation).join(", ")}`);
1935
+ const lines = [`${event.status === "validated" ? "Validated" : "Rejected"} survey asset change: ${event.summary}`];
1949
1936
  if (event.status === "validated") {
1950
- lines.push("Lifecycle: this candidate is registered, not live. The embedded image bytes remain available to the application and were omitted from model context.");
1937
+ lines.push(`Draft updated (${event.operations.length} total operation${event.operations.length === 1 ? "" : "s"}); the live editor remains unchanged until turn completion. Embedded image bytes remain available to the application and were omitted from model context.`);
1951
1938
  const importedAssetIds = event.operations.filter((operation) => operation.kind === "set-image-asset").map((operation) => operation.assetId);
1952
1939
  if (importedAssetIds.length > 0) {
1940
+ lines.push(`Imported: ${event.operations.filter((operation) => operation.kind === "set-image-asset").map(describeAssetOperation).join(", ")}`);
1953
1941
  lines.push(`Asset id to reference: ${importedAssetIds.join(", ")}`);
1954
- lines.push("Required continuation: pass continuationMode \"append\" on every following mutation that should keep this asset import. To use the image in a NEW item, create that item in one survey_change call and put the image block directly in create-item.translations[].content; do not create placeholder text and do not call update-placed-image for an item that is not in the immutable snapshot. For an existing rich-text image placement, use update-placed-image. If a later proposal is rejected, re-import the attachment with continuationMode \"replace\", then append one corrected dependent survey_change; never replace the turn with only the asset reference.");
1942
+ 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.");
1955
1943
  lines.push(`New info-item image source: { "type": "asset", "assetId": "${importedAssetIds[0]}" }. Embed it in a richText image block inside the create-item translation content.`);
1956
1944
  lines.push(`New info-item translation example: { "locale": "<selected-locale>", "contentKey": "content", "content": { "type": "richText", "version": 1, "doc": { "type": "doc", "blocks": [{ "type": "image", "source": { "type": "asset", "assetId": "${importedAssetIds[0]}" }, "alt": "<meaningful description>" }] } } }.`);
1957
1945
  }
@@ -1980,7 +1968,7 @@ const redactAssetChangeForTranscript = (event) => ({
1980
1968
  });
1981
1969
  const surveyAssetTool = createTool({
1982
1970
  id: TOOL_ID,
1983
- description: "Import an attached image into survey assets without asking the model to copy base64, or update/remove an existing image asset. The result is an undoable candidate change, not a live asset. After importing, pass continuationMode \"append\" to the dependent mutation so the import and references are authorized together. For a new item, embed the asset image block directly in create-item.translations[].content in that one dependent call; update-placed-image is only for an image placement that already exists in the immutable snapshot. This tool refuses to remove a currently referenced asset. To delete references and their asset together, use one survey_change candidate with reference-removal operations first and remove-image-asset last.",
1971
+ description: "Import an attached image into the request-scoped survey draft, or update/remove an image asset. Successful calls accumulate automatically. Later mutations may reference an imported asset id. To delete references and an asset atomically, use one survey_change delta with reference removals first and remove-image-asset last.",
1984
1972
  inputSchema,
1985
1973
  outputSchema: surveyChangeSetSchema,
1986
1974
  mcp: { annotations: {
@@ -1992,8 +1980,8 @@ const surveyAssetTool = createTool({
1992
1980
  } },
1993
1981
  execute: async (input, context) => {
1994
1982
  const requestContext = context?.requestContext;
1995
- const { changeCollector, turnId } = requireSurveyAssistantTurnState(requestContext);
1996
- const snapshot = requestContext?.get("engineContext");
1983
+ const { turnDraft, turnId } = requireSurveyAssistantTurnState(requestContext);
1984
+ const snapshot = getSurveyAssistantEngineContext(requestContext);
1997
1985
  let operation;
1998
1986
  if (input.action === "import-attachment") {
1999
1987
  if (!input.documentId) throw new Error("import-attachment requires documentId.");
@@ -2006,7 +1994,8 @@ const surveyAssetTool = createTool({
2006
1994
  const base = input.assetId ?? slug(manifest.name);
2007
1995
  let assetId = base;
2008
1996
  let suffix = 2;
2009
- while (snapshot?.survey.assets?.[assetId]) assetId = `${base}-${suffix++}`;
1997
+ const stagedAssetIds = new Set(turnDraft.draftOperations.flatMap((draftOperation) => draftOperation.kind === "set-image-asset" ? [draftOperation.assetId] : []));
1998
+ while (snapshot?.survey.assets?.[assetId] || stagedAssetIds.has(assetId)) assetId = `${base}-${suffix++}`;
2010
1999
  operation = {
2011
2000
  kind: "set-image-asset",
2012
2001
  assetId,
@@ -2060,14 +2049,19 @@ const surveyAssetTool = createTool({
2060
2049
  issues: [...event.validation.issues, {
2061
2050
  severity: "error",
2062
2051
  code: "image-asset-still-referenced",
2063
- message: `Image asset "${operation.assetId}" is still referenced by ${referenceLabels.slice(0, 3).join(", ")}. Prepare one survey_change candidate containing every reference-removal operation first and remove-image-asset last. If replacing earlier candidates in this turn, that combined candidate must use continuationMode "replace".`,
2052
+ message: `Image asset "${operation.assetId}" is still referenced by ${referenceLabels.slice(0, 3).join(", ")}. Prepare one survey_change delta containing every reference-removal operation first and remove-image-asset last.`,
2064
2053
  operationIndex: 0
2065
2054
  }],
2066
2055
  normalizedOperations: []
2067
2056
  }
2068
2057
  });
2058
+ if (event.status === "rejected") return turnDraft.reject(event, [`asset:${operation.assetId}`]);
2069
2059
  }
2070
- return changeCollector.register(event, { continuationMode: input.continuationMode });
2060
+ return turnDraft.stage({
2061
+ operations: [operation],
2062
+ snapshot,
2063
+ summary: input.summary
2064
+ });
2071
2065
  },
2072
2066
  transform: { transcript: { output: ({ output }) => output ? redactAssetChangeForTranscript(getToolOutput(output)) : output } },
2073
2067
  toModelOutput: (value) => toTextModelOutput(value, summarizeAssetChange)
@@ -2095,4 +2089,4 @@ const surveyAssistantTools = Object.freeze({
2095
2089
  //#endregion
2096
2090
  export { surveyAuditTool as i, surveyAssistantTools as n, surveyAssetTool as r, SURVEY_ASSISTANT_TOOL_IDS as t };
2097
2091
 
2098
- //# sourceMappingURL=tools-DDXaJ8Za.mjs.map
2092
+ //# sourceMappingURL=tools-wgLen05n.mjs.map