@superdoc/mcp 1.0.0-next.7 → 1.0.0-next.9

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 (2) hide show
  1. package/dist/index.js +419 -104
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -349815,7 +349815,7 @@ var init_host = __esm(() => {
349815
349815
  });
349816
349816
 
349817
349817
  // ../../packages/sdk/langs/node/src/runtime/sdk-version.generated.ts
349818
- var SDK_VERSION = "2.13.0-next.9";
349818
+ var SDK_VERSION = "2.13.0-next.11";
349819
349819
 
349820
349820
  // ../../packages/sdk/langs/node/src/runtime/embedded-platform.ts
349821
349821
  import { chmodSync, existsSync } from "node:fs";
@@ -489460,7 +489460,8 @@ function tryRangeReplacementAcrossTokens(input, allTokens, touched) {
489460
489460
  if (touchedInsertions.some(({ token }) => token.innerRuns && token.innerRuns.length > 1 || token.nestedDeletions && token.nestedDeletions.length > 0)) {
489461
489461
  return null;
489462
489462
  }
489463
- return input.changeMode === "tracked" ? authorTrackedPureDelete(input, allTokens, touched) : authorDirectPureDelete(input, allTokens, touched);
489463
+ const result = input.changeMode === "tracked" ? authorTrackedPureDelete(input, allTokens, touched) : authorDirectPureDelete(input, allTokens, touched);
489464
+ return result?.ok && input.changeMode === "tracked" ? { ...result, requiresCollaborationSessionSync: true } : result;
489464
489465
  }
489465
489466
  function renderBreakRunPureDelete(token, input, mode, wrapDeleted) {
489466
489467
  const { controlXml, runOpen, runClose, rPr, text, controlVisibleStart } = token.inertBreakRun;
@@ -491550,11 +491551,16 @@ function buildCoalescedDeletionReplacement(change, partXml) {
491550
491551
  return null;
491551
491552
  if (index > 0) {
491552
491553
  const previous = orderedFragments[index - 1];
491553
- xml += partXml.slice(previous.byteEnd, fragment2.byteStart);
491554
+ const interstitialXml = partXml.slice(previous.byteEnd, fragment2.byteStart);
491555
+ if (/<(?:[\w.-]+:)?(?:fldChar|fldSimple|instrText)\b/.test(interstitialXml))
491556
+ return null;
491557
+ xml += interstitialXml;
491554
491558
  }
491555
491559
  xml += parts.innerXml;
491556
491560
  }
491557
491561
  xml += firstParts.closeTag;
491562
+ if (parseXmlForError(`<root>${xml}</root>`, { namespaceAware: false }) !== null)
491563
+ return null;
491558
491564
  return { start: first.byteStart, end: last.byteEnd, xml };
491559
491565
  }
491560
491566
  function readTrackedWrapperParts(partXml, fragment2, expectedKind) {
@@ -491689,6 +491695,7 @@ function escapeRegExp6(value) {
491689
491695
  }
491690
491696
  var DECODER, ENCODER;
491691
491697
  var init_export_projection = __esm(() => {
491698
+ init_xml_sax();
491692
491699
  init_catalog3();
491693
491700
  init_logical_catalog();
491694
491701
  init_move_stamps();
@@ -493652,6 +493659,9 @@ function commitTracked(input, view) {
493652
493659
  }
493653
493660
  if (!localized.ok)
493654
493661
  return rejected2(txId, localized.reason);
493662
+ if (compileOp.on.coordinateSpace !== "tracked" && requiresStructuralTrackedRewriteSessionSync(paragraphXml, inlineContext.model.leaves(), compileOp.on.startOffset, compileOp.on.endOffset)) {
493663
+ localized = { ...localized, requiresCollaborationSessionSync: true };
493664
+ }
493655
493665
  if (op.newText.includes("\t"))
493656
493666
  localized = { ...localized, requiresCollaborationSessionSync: true };
493657
493667
  if (localized.foldedInsRsid != null) {
@@ -494691,6 +494701,36 @@ function rewriteLocalizedDirect(bytes, window2, op, inheritedNamespaceBindings =
494691
494701
  }
494692
494702
  return { ok: false, reason: plan.reason };
494693
494703
  }
494704
+ function requiresStructuralTrackedRewriteSessionSync(paragraphXml, leaves, startOffset, endOffset) {
494705
+ if (startOffset >= endOffset)
494706
+ return false;
494707
+ const transparentMarkerRe = /<[\w.-]+:(?:proofErr|lastRenderedPageBreak)\b[^>]*\/?>/g;
494708
+ for (const match of paragraphXml.matchAll(transparentMarkerRe)) {
494709
+ const markerStart = match.index;
494710
+ const markerEnd = markerStart + match[0].length;
494711
+ let before;
494712
+ for (let index = leaves.length - 1;index >= 0; index -= 1) {
494713
+ if (leaves[index].charEnd <= markerStart) {
494714
+ before = leaves[index];
494715
+ break;
494716
+ }
494717
+ }
494718
+ const after = leaves.find((piece) => piece.charStart >= markerEnd);
494719
+ const markerOffset = before?.visibleEnd ?? after?.visibleStart;
494720
+ if (markerOffset != null && startOffset < markerOffset && endOffset > markerOffset)
494721
+ return true;
494722
+ }
494723
+ const affected = leaves.filter((piece) => {
494724
+ const overlapsVisibleRange = startOffset < piece.visibleEnd && endOffset > piece.visibleStart;
494725
+ const crossesZeroWidthPiece = startOffset < piece.visibleStart && endOffset > piece.visibleEnd;
494726
+ return overlapsVisibleRange || crossesZeroWidthPiece;
494727
+ });
494728
+ if (affected.some((piece) => piece.kind !== "text" && piece.kind !== "control"))
494729
+ return true;
494730
+ const structuralStack = (piece) => JSON.stringify(piece.containerStack.filter((scope) => scope.kind !== "ins" && scope.kind !== "del" && scope.kind !== "moveFrom" && scope.kind !== "moveTo").map((scope) => [scope.kind, scope.qname]));
494731
+ const textStacks = affected.filter((piece) => piece.kind === "text").map(structuralStack);
494732
+ return textStacks.some((stack) => stack !== textStacks[0]);
494733
+ }
494694
494734
  function rewriteLocalizedTracked(bytes, window2, op, stamp, preparedContext) {
494695
494735
  const paragraphBytes = bytes.subarray(window2.byteStart, window2.byteEnd);
494696
494736
  const inlineContext = preparedContext ?? prepareParagraphInlineEditContext(paragraphBytes);
@@ -496464,7 +496504,7 @@ function rewriteRunSpanTrackedReplace(paragraphXml, startOffset, endOffset, stam
496464
496504
  return { ok: false, reason: "tracked-inline-control-unsupported" };
496465
496505
  }
496466
496506
  }
496467
- if (collected.markerIntegrity.hasPreservableMarker) {
496507
+ if (collected.markerIntegrity.hasPreservableMarker || stamp.newText.length > 0 && requiresStructuralTrackedRewriteSessionSync(paragraphXml, parseParagraphInlineMap(paragraphXml).leaves(), startOffset, endOffset)) {
496468
496508
  return rewriteRunSpanTrackedReplacePreservingMarkers(paragraphXml, span, stamp, paragraphMarkFallback.rPr);
496469
496509
  }
496470
496510
  const first = span.runs[span.firstRunIdx];
@@ -503109,7 +503149,7 @@ function runTrackedChangeDecideCommit(input) {
503109
503149
  fragmentsByPart,
503110
503150
  structuralTargets
503111
503151
  });
503112
- const sessionSyncedTextDecision = trackedTextDecide && (hasNestedTextDecision(catalog, fragmentsByPart) || hasStructuralInlineDeletionDecision(state, fragmentsByPart));
503152
+ const sessionSyncedTextDecision = trackedTextDecide && (hasNestedTextDecision(catalog, fragmentsByPart) || hasStructuralInlineDeletionDecision(state, fragmentsByPart) || hasMarkerSeparatedTextDecision(state, fragmentsByPart));
503113
503153
  const prepared = {
503114
503154
  txId,
503115
503155
  source: op,
@@ -503479,6 +503519,45 @@ function hasStructuralInlineDeletionDecision(state, fragmentsByPart) {
503479
503519
  }
503480
503520
  return false;
503481
503521
  }
503522
+ function hasMarkerSeparatedTextDecision(state, fragmentsByPart) {
503523
+ for (const [partUri, fragments] of fragmentsByPart) {
503524
+ const partBytes = state.packageBytes.get(partUri);
503525
+ if (!partBytes)
503526
+ continue;
503527
+ const textFragments = fragments.filter((fragment2) => fragment2.kind === "ins" || fragment2.kind === "del");
503528
+ const paragraphByFragment = findParagraphsContainingFragments(state, textFragments);
503529
+ const grouped = new Map;
503530
+ for (const fragment2 of textFragments) {
503531
+ const paragraph = paragraphByFragment.get(fragment2);
503532
+ if (!paragraph)
503533
+ continue;
503534
+ const key2 = `${fragment2.storyId}|${paragraph.blockId}|${fragment2.kind}|${fragment2.rsid}`;
503535
+ const group = grouped.get(key2) ?? [];
503536
+ group.push(fragment2);
503537
+ grouped.set(key2, group);
503538
+ }
503539
+ for (const group of grouped.values()) {
503540
+ if (group.length < 2)
503541
+ continue;
503542
+ const ordered = [...group].sort((left, right) => {
503543
+ const leftRange = preparedFragmentSourceRange(left);
503544
+ const rightRange = preparedFragmentSourceRange(right);
503545
+ return leftRange.start - rightRange.start;
503546
+ });
503547
+ for (let index = 1;index < ordered.length; index += 1) {
503548
+ const previous = preparedFragmentSourceRange(ordered[index - 1]);
503549
+ const current = preparedFragmentSourceRange(ordered[index]);
503550
+ if (!previous || !current || previous.end > current.start)
503551
+ continue;
503552
+ const separator = TD44.decode(partBytes.subarray(previous.end, current.start));
503553
+ if (/<[\w.-]+:(?:bookmarkStart|bookmarkEnd|commentRangeStart|commentRangeEnd|commentReference)\b/.test(separator)) {
503554
+ return true;
503555
+ }
503556
+ }
503557
+ }
503558
+ }
503559
+ return false;
503560
+ }
503482
503561
  function buildPreparedTrackedTextDecide(input) {
503483
503562
  if (input.op.targets.some((target) => target.partial || target.formatting || target.derivedStructural || (target.moveRangeMarkers?.ids.length ?? 0) > 0)) {
503484
503563
  return;
@@ -691770,7 +691849,7 @@ function parseParagraphXml(xml, inheritedNamespaceBindings = {}, trackedWrapperI
691770
691849
  } else if (tagLocalName === "hyperlink") {
691771
691850
  const inner = stripOuterTag(next.xml);
691772
691851
  const closingTag = extractClosingTag(next.xml);
691773
- const innerSegments = parseInnerInline(inner, namespaceBindingsForElement(next.openTagInner, paragraphNamespaceBindings));
691852
+ const innerSegments = parseInnerInline(inner, namespaceBindingsForElement(next.openTagInner, paragraphNamespaceBindings), trackedWrapperIdentityState);
691774
691853
  segments.push({
691775
691854
  kind: "atom",
691776
691855
  atomKind: INLINE_ATOM_KIND.preserved,
@@ -707175,44 +707254,130 @@ function syncParagraphMarkerCarriers(carriers, next, create10) {
707175
707254
  offset += carrier.text.length;
707176
707255
  return { ...carrier, start, end: offset, next: [] };
707177
707256
  });
707178
- for (const marker of next) {
707179
- const position2 = Number(marker.visibleOffset ?? 0);
707180
- const owner = windows.find((window2) => window2.markers.toArray().some((existing) => readInlineControlOffset(existing) + window2.start === position2 && sameMarker(existing, marker))) ?? windows.find((window2) => position2 < window2.end) ?? windows[windows.length - 1];
707181
- owner.next.push({ ...marker, visibleOffset: position2 - owner.start });
707257
+ for (let markerIndex = 0;markerIndex < next.length; ) {
707258
+ const position2 = Number(next[markerIndex].visibleOffset ?? 0);
707259
+ let groupEnd = markerIndex + 1;
707260
+ while (groupEnd < next.length && Number(next[groupEnd].visibleOffset ?? 0) === position2)
707261
+ groupEnd += 1;
707262
+ const group = next.slice(markerIndex, groupEnd);
707263
+ const retainedOwners = windows.filter((window2) => group.some((marker) => window2.markers.toArray().some((existing) => readInlineControlOffset(existing) + window2.start === position2 && sameMarker(existing, marker))));
707264
+ const sharedOwner = retainedOwners.length === 1 ? retainedOwners[0] : null;
707265
+ for (const marker of group) {
707266
+ const owner = sharedOwner ?? windows.find((window2) => window2.markers.toArray().some((existing) => readInlineControlOffset(existing) + window2.start === position2 && sameMarker(existing, marker))) ?? windows.find((window2) => position2 < window2.end) ?? windows[windows.length - 1];
707267
+ owner.next.push({ ...marker, visibleOffset: position2 - owner.start });
707268
+ }
707269
+ markerIndex = groupEnd;
707182
707270
  }
707183
707271
  for (const window2 of windows)
707184
707272
  syncParagraphMarkers(window2.markers, window2.next, create10);
707185
707273
  }
707186
707274
  function syncParagraphMarkers(array3, next, create10) {
707187
707275
  const previous3 = array3.toArray();
707188
- let prefix = 0;
707189
- while (prefix < previous3.length && prefix < next.length && sameMarker(previous3[prefix], next[prefix]))
707190
- prefix += 1;
707191
- let suffix = 0;
707192
- while (suffix < previous3.length - prefix && suffix < next.length - prefix && sameMarker(previous3[previous3.length - suffix - 1], next[next.length - suffix - 1]))
707193
- suffix += 1;
707194
- const removed = previous3.length - prefix - suffix;
707195
- if (removed > 0)
707196
- array3.delete(prefix, removed);
707197
- const additions = next.slice(prefix, next.length - suffix);
707198
- const inserted = additions.map(create10);
707199
- if (inserted.length > 0)
707200
- array3.insert(prefix, inserted);
707276
+ const retained = longestSharedMarkerSubsequence(previous3, next);
707277
+ const anchors2 = [
707278
+ { previous: -1, next: -1 },
707279
+ ...retained,
707280
+ { previous: previous3.length, next: next.length }
707281
+ ];
707282
+ for (let anchorIndex = anchors2.length - 2;anchorIndex >= 0; anchorIndex -= 1) {
707283
+ const left = anchors2[anchorIndex];
707284
+ const right = anchors2[anchorIndex + 1];
707285
+ const start = left.previous + 1;
707286
+ const removed = right.previous - start;
707287
+ if (removed > 0)
707288
+ array3.delete(start, removed);
707289
+ const additions = next.slice(left.next + 1, right.next);
707290
+ const inserted = additions.map(create10);
707291
+ if (inserted.length > 0)
707292
+ array3.insert(start, inserted);
707293
+ assignInsertedTrackedMarkerIds(array3, inserted, additions, start);
707294
+ }
707295
+ next.forEach((value2, index2) => {
707296
+ const marker = array3.get(index2);
707297
+ if (marker.get("visibleOffset") !== value2.visibleOffset)
707298
+ marker.set("visibleOffset", value2.visibleOffset);
707299
+ anchorInlineControl(marker, Number(value2.visibleOffset));
707300
+ });
707301
+ }
707302
+ function longestSharedMarkerSubsequence(previous3, next) {
707303
+ const exactCells = (previous3.length + 1) * (next.length + 1);
707304
+ if (exactCells > 1e6)
707305
+ return orderedSharedMarkerSubsequence(previous3, next);
707306
+ const lengths = Array.from({ length: previous3.length + 1 }, () => new Uint32Array(next.length + 1));
707307
+ for (let previousIndex2 = previous3.length - 1;previousIndex2 >= 0; previousIndex2 -= 1) {
707308
+ for (let nextIndex2 = next.length - 1;nextIndex2 >= 0; nextIndex2 -= 1) {
707309
+ lengths[previousIndex2][nextIndex2] = sameMarker(previous3[previousIndex2], next[nextIndex2]) ? 1 + lengths[previousIndex2 + 1][nextIndex2 + 1] : Math.max(lengths[previousIndex2 + 1][nextIndex2], lengths[previousIndex2][nextIndex2 + 1]);
707310
+ }
707311
+ }
707312
+ const retained = [];
707313
+ let previousIndex = 0;
707314
+ let nextIndex = 0;
707315
+ while (previousIndex < previous3.length && nextIndex < next.length) {
707316
+ if (sameMarker(previous3[previousIndex], next[nextIndex]) && lengths[previousIndex][nextIndex] === 1 + lengths[previousIndex + 1][nextIndex + 1]) {
707317
+ retained.push({ previous: previousIndex, next: nextIndex });
707318
+ previousIndex += 1;
707319
+ nextIndex += 1;
707320
+ } else if (lengths[previousIndex + 1][nextIndex] >= lengths[previousIndex][nextIndex + 1]) {
707321
+ previousIndex += 1;
707322
+ } else {
707323
+ nextIndex += 1;
707324
+ }
707325
+ }
707326
+ return retained;
707327
+ }
707328
+ function orderedSharedMarkerSubsequence(previous3, next) {
707329
+ const positions = new Map;
707330
+ previous3.forEach((marker, index2) => {
707331
+ const key2 = markerDiscriminator((name) => marker.get(name));
707332
+ const indexes = positions.get(key2);
707333
+ if (indexes)
707334
+ indexes.push(index2);
707335
+ else
707336
+ positions.set(key2, [index2]);
707337
+ });
707338
+ const retained = [];
707339
+ let minimumPrevious = 0;
707340
+ next.forEach((marker, nextIndex) => {
707341
+ const indexes = positions.get(markerDiscriminator((name) => marker[name]));
707342
+ if (!indexes)
707343
+ return;
707344
+ let low = 0;
707345
+ let high = indexes.length;
707346
+ while (low < high) {
707347
+ const middle = low + high >>> 1;
707348
+ if (indexes[middle] < minimumPrevious)
707349
+ low = middle + 1;
707350
+ else
707351
+ high = middle;
707352
+ }
707353
+ while (low < indexes.length && !sameMarker(previous3[indexes[low]], marker))
707354
+ low += 1;
707355
+ if (low === indexes.length)
707356
+ return;
707357
+ const previousIndex = indexes[low];
707358
+ retained.push({ previous: previousIndex, next: nextIndex });
707359
+ minimumPrevious = previousIndex + 1;
707360
+ });
707361
+ return retained;
707362
+ }
707363
+ function markerDiscriminator(read) {
707364
+ return JSON.stringify([
707365
+ read("kind"),
707366
+ read("xml"),
707367
+ read("length"),
707368
+ read("meta")
707369
+ ]);
707370
+ }
707371
+ function assignInsertedTrackedMarkerIds(array3, inserted, additions, start) {
707201
707372
  inserted.forEach((marker, index2) => {
707202
707373
  const value2 = additions[index2];
707203
707374
  const meta3 = value2.meta;
707204
707375
  const trackedCarrier = value2.kind === "tracked-wrapper" || value2.kind === "preserved" && (meta3?.tag === "delTextRun" || meta3?.container === "tracked-deletion");
707205
707376
  if (trackedCarrier) {
707206
- const item = createRelativePositionFromTypeIndex(array3, prefix + index2).item;
707377
+ const item = createRelativePositionFromTypeIndex(array3, start + index2).item;
707207
707378
  marker.set("id", `${value2.id}:sync:${item.client.toString(36)}:${item.clock.toString(36)}`);
707208
707379
  }
707209
707380
  });
707210
- next.forEach((value2, index2) => {
707211
- const marker = array3.get(index2);
707212
- if (marker.get("visibleOffset") !== value2.visibleOffset)
707213
- marker.set("visibleOffset", value2.visibleOffset);
707214
- anchorInlineControl(marker, Number(value2.visibleOffset));
707215
- });
707216
707381
  }
707217
707382
  var init_paragraph_inline_sync = __esm(() => {
707218
707383
  init_yjs();
@@ -707239,13 +707404,14 @@ function mirrorStorySurfaceFromSession(input) {
707239
707404
  };
707240
707405
  }
707241
707406
  try {
707407
+ const extension2 = storyScopedTrackedDecisionExtension(input.extension, storyId);
707242
707408
  const scope = resolveStoryShardScope(doc2);
707243
707409
  scope.doc.transact(() => {
707244
- const groups = input.extension.contentControlRewrite || hasContentControlGroups(scope) ? prepareStoryContentControlGroups(input.session, storyId, doc2) : null;
707410
+ const groups = extension2.contentControlRewrite || hasContentControlGroups(scope) ? prepareStoryContentControlGroups(input.session, storyId, doc2) : null;
707245
707411
  mirrorFamilyIntoStory({
707246
707412
  session: input.session,
707247
707413
  prepared: input.prepared,
707248
- extension: input.extension,
707414
+ extension: extension2,
707249
707415
  paragraphOnlyReviewStoryIds: input.paragraphOnlyReviewStoryIds,
707250
707416
  storyId,
707251
707417
  doc: doc2
@@ -707265,6 +707431,16 @@ function mirrorStorySurfaceFromSession(input) {
707265
707431
  }
707266
707432
  return { mirrored: true };
707267
707433
  }
707434
+ function storyScopedTrackedDecisionExtension(extension2, storyId) {
707435
+ if (extension2.family !== "storyTrackedDecide" || !extension2.trackedTextDecide)
707436
+ return extension2;
707437
+ const targets = extension2.trackedTextDecide.targets.filter((target) => target.storyId === storyId);
707438
+ return {
707439
+ ...extension2,
707440
+ targetBlockIds: [...new Set(targets.map((target) => target.blockId))],
707441
+ trackedTextDecide: { ...extension2.trackedTextDecide, targets }
707442
+ };
707443
+ }
707268
707444
  function prepareStoryContentControlGroups(session, storyId, doc2) {
707269
707445
  const scope = resolveStoryShardScope(doc2);
707270
707446
  const source = readContentControlBlockGroups(loadStorySyncContext(session, storyId).bytes);
@@ -708730,7 +708906,7 @@ function runStorySurfaceMirror(prepared, options, admission, paragraphOnlyReview
708730
708906
  };
708731
708907
  }
708732
708908
  }
708733
- const familyResult = runFamilyMirror(prepared, extension2, options, admission.directStoryText, paragraphOnlyReviewStoryIds);
708909
+ const familyResult = runFamilyMirror(prepared, extension2, options, admission.directStoryText, admission.trackedDecision, paragraphOnlyReviewStoryIds);
708734
708910
  if (!familyResult.mirrored) {
708735
708911
  return {
708736
708912
  txId: prepared.txId,
@@ -708769,7 +708945,7 @@ function hasStrictMirrorPrerequisites(prepared, options) {
708769
708945
  case "storyText":
708770
708946
  return !extension2.historyReplay && !extension2.resyncFromSession && !extension2.sessionSync ? true : options.session != null;
708771
708947
  case "storyTrackedDecide":
708772
- return !usesTrackedDecisionSessionSync(prepared, extension2) || options.session != null;
708948
+ return !usesTrackedDecisionSessionSync(prepared, extension2, options) || options.session != null;
708773
708949
  case "storyTable":
708774
708950
  return true;
708775
708951
  case "storyBlocks":
@@ -708788,7 +708964,7 @@ function hasStrictMirrorPrerequisites(prepared, options) {
708788
708964
  }
708789
708965
  }
708790
708966
  }
708791
- function runFamilyMirror(prepared, extension2, options, directStoryText, paragraphOnlyReviewStoryIds) {
708967
+ function runFamilyMirror(prepared, extension2, options, directStoryText, trackedDecision, paragraphOnlyReviewStoryIds) {
708792
708968
  if (!options.resolveStoryShard) {
708793
708969
  return {
708794
708970
  mirrored: false,
@@ -708836,7 +709012,7 @@ function runFamilyMirror(prepared, extension2, options, directStoryText, paragra
708836
709012
  return { mirrored: true };
708837
709013
  }
708838
709014
  if (extension2.family === "storyTrackedDecide") {
708839
- if (usesTrackedDecisionSessionSync(prepared, extension2)) {
709015
+ if (usesTrackedDecisionSessionSync(prepared, extension2, options)) {
708840
709016
  if (!options.session) {
708841
709017
  return {
708842
709018
  mirrored: false,
@@ -708850,14 +709026,32 @@ function runFamilyMirror(prepared, extension2, options, directStoryText, paragra
708850
709026
  const sessionSync = mirrorStorySurfaceFromSession({
708851
709027
  session: options.session,
708852
709028
  prepared,
708853
- extension: extension2,
709029
+ extension: extension2.sessionSync || !extension2.trackedTextDecide ? extension2 : { ...extension2, sessionSync: { reason: "structural-inline-edit" } },
708854
709030
  storyIds: storyIdsRequiringShards(prepared, extension2),
708855
709031
  paragraphOnlyReviewStoryIds,
708856
709032
  resolveStoryShard: options.resolveStoryShard
708857
709033
  });
709034
+ if (sessionSync.mirrored && extension2.trackedTextDecide) {
709035
+ for (const target of extension2.trackedTextDecide.targets) {
709036
+ const doc2 = options.resolveStoryShard(target.storyId);
709037
+ if (doc2) {
709038
+ recordTrackedDecision(doc2, target.rsid, extension2.trackedTextDecide.decision, prepared.txId);
709039
+ }
709040
+ }
709041
+ }
708858
709042
  return { ...sessionSync, mirrorPath: "session-sync" };
708859
709043
  }
708860
- return mirrorPreparedTrackedDecide(prepared, extension2, options);
709044
+ if (!trackedDecision) {
709045
+ return {
709046
+ mirrored: false,
709047
+ mirrorPath: "story-tracked-decide",
709048
+ failure: {
709049
+ storyId: prepared.resolvedTargets.stories[0] ?? "",
709050
+ message: "storyTrackedDecide requires an admitted cross-story preflight"
709051
+ }
709052
+ };
709053
+ }
709054
+ return mirrorPreparedTrackedDecide(prepared, trackedDecision);
708861
709055
  }
708862
709056
  if (extension2.family === "compareApply") {
708863
709057
  return {
@@ -709072,30 +709266,11 @@ function mirrorPreparedCommentInsert(prepared, extension2, options) {
709072
709266
  });
709073
709267
  return { mirrored: true, mirrorPath: "session-sync" };
709074
709268
  }
709075
- function mirrorPreparedTrackedDecide(prepared, extension2, options) {
709076
- const validation = validatePreparedTrackedDecide(prepared, extension2, options);
709077
- if (!validation.ok) {
709078
- return {
709079
- mirrored: false,
709080
- mirrorPath: "story-tracked-decide",
709081
- failure: {
709082
- storyId: prepared.resolvedTargets.stories[0] ?? "",
709083
- message: validation.failureReason
709084
- }
709085
- };
709086
- }
709087
- const source = validation.source;
709088
- const storyDocs = validation.storyDocs;
709089
- for (const { storyId, doc: doc2 } of storyDocs) {
709090
- const targets = validation.targets.filter((target) => target.storyId === storyId);
709091
- if (targets.length === 0)
709092
- continue;
709093
- const result = applyTrackedTextDecisionsToStory({
709094
- doc: doc2,
709095
- targets,
709096
- decision: source.decision,
709097
- txId: prepared.txId,
709098
- removedCommentIds: collectInvalidatedCommentIds(prepared)
709269
+ function mirrorPreparedTrackedDecide(prepared, preflight2) {
709270
+ for (const { storyId, plan } of preflight2.stories) {
709271
+ const result = applyPreflightedTrackedTextDecisionsToStory({
709272
+ plan,
709273
+ txId: prepared.txId
709099
709274
  });
709100
709275
  if (!result.ok) {
709101
709276
  return {
@@ -709169,6 +709344,26 @@ function validatePreparedTrackedDecide(prepared, extension2, options) {
709169
709344
  }
709170
709345
  return { ok: true, source, storyDocs, targets: trackedTextDecide.targets };
709171
709346
  }
709347
+ function preflightPreparedTrackedDecide(input) {
709348
+ const stories = [];
709349
+ const removedCommentIds = collectInvalidatedCommentIds(input.prepared);
709350
+ for (const { storyId, doc: doc2 } of input.storyDocs) {
709351
+ const targets = input.targets.filter((target) => target.storyId === storyId);
709352
+ if (targets.length === 0)
709353
+ continue;
709354
+ const result = preflightTrackedTextDecisionsToStory({
709355
+ doc: doc2,
709356
+ targets,
709357
+ decision: input.source.decision,
709358
+ removedCommentIds
709359
+ });
709360
+ if (!result.ok) {
709361
+ return { ok: false, failureReason: `${storyId}: ${result.failureReason}` };
709362
+ }
709363
+ stories.push({ storyId, plan: result.plan });
709364
+ }
709365
+ return { ok: true, preflight: { stories } };
709366
+ }
709172
709367
  function isSupportedPreparedTrackedTextKind(kind) {
709173
709368
  return kind === "ins" || kind === "del";
709174
709369
  }
@@ -709205,47 +709400,133 @@ function isSupportedPreparedTrackedCarrier(target) {
709205
709400
  return true;
709206
709401
  return target.carrierKind === "field-insertion" && target.trackedKind === "ins";
709207
709402
  }
709208
- function applyTrackedTextDecisionsToStory(input) {
709403
+ function preflightTrackedTextDecisionsToStory(input) {
709209
709404
  const resolved = [];
709210
- for (const target of input.targets) {
709211
- const spans = findPreparedLiveTrackedTextSpans(input.doc, target);
709212
- const alreadyDecided = spans.length === 0 && hasTrackedDecisionLedgerEntry(input.doc, target.rsid);
709213
- if (spans.length === 0 && !alreadyDecided) {
709405
+ const usedSpanIds = new Set;
709406
+ const candidatesByTarget = input.targets.map((target) => ({
709407
+ target,
709408
+ spans: findPreparedLiveTrackedTextSpans(input.doc, target)
709409
+ }));
709410
+ for (const { target, spans } of candidatesByTarget) {
709411
+ const span = spans.find((candidate) => !usedSpanIds.has(liveTrackedTextSpanId(candidate)));
709412
+ const alreadyDecided = !span && spans.length === 0 && hasTrackedDecisionLedgerEntry(input.doc, target.rsid);
709413
+ if (!span && !alreadyDecided) {
709214
709414
  return { ok: false, failureReason: `tracked decision target ${target.rsid} is not a live tracked text change` };
709215
709415
  }
709216
- resolved.push({ target, spans });
709416
+ if (span)
709417
+ usedSpanIds.add(liveTrackedTextSpanId(span));
709418
+ resolved.push({ target, spans: span ? [span] : [] });
709419
+ }
709420
+ for (const { target, spans } of candidatesByTarget) {
709421
+ if (spans.some((span) => !usedSpanIds.has(liveTrackedTextSpanId(span)))) {
709422
+ return {
709423
+ ok: false,
709424
+ failureReason: `tracked decision target ${target.rsid} metadata does not cover every matching live carrier`
709425
+ };
709426
+ }
709217
709427
  }
709218
709428
  const live = resolved.filter((entry) => entry.spans.length > 0);
709219
- if (live.length === 0)
709429
+ try {
709430
+ const removedCommentCarrierIds = collectCommentCarrierIds(input.removedCommentIds);
709431
+ const work = live.flatMap((entry) => entry.spans.map((span) => {
709432
+ const block = findBlockMap(input.doc, span.blockId)?.block;
709433
+ if (!block)
709434
+ throw new Error(`target block ${span.blockId} is not present in the story shard`);
709435
+ return {
709436
+ target: entry.target,
709437
+ span,
709438
+ block,
709439
+ leftBoundaryAtoms: [],
709440
+ leftBoundaryAnchors: []
709441
+ };
709442
+ }));
709443
+ for (const item of work) {
709444
+ const { target, span, block, leftBoundaryAtoms, leftBoundaryAnchors } = item;
709445
+ const blockLength = getBlockText(block).length;
709446
+ if (span.startOffset < 0 || span.startOffset > blockLength) {
709447
+ throw new Error(`tracked text span ${target.rsid} starts outside block ${span.blockId}`);
709448
+ }
709449
+ if (span.trackedKind === "ins" && input.decision === "reject") {
709450
+ if (span.endOffset < span.startOffset || span.endOffset > blockLength) {
709451
+ throw new Error(`tracked insert span ${target.rsid} is outside block ${span.blockId}`);
709452
+ }
709453
+ }
709454
+ if (span.trackedKind === "del" && input.decision === "reject") {
709455
+ let reachedStart = false;
709456
+ const removedTrackedCarrierIds = trackedTextCarrierIds(span, false);
709457
+ getBlockAtoms(block).forEach((atom) => {
709458
+ if (reachedStart)
709459
+ return;
709460
+ const atomId = String(atom.get(ATOM_KEY.id) ?? "");
709461
+ if (atomId === span.startAtomId) {
709462
+ reachedStart = true;
709463
+ return;
709464
+ }
709465
+ if (atomId && Number(atom.get(ATOM_KEY.visibleOffset) ?? -1) === span.startOffset && Number(atom.get(ATOM_KEY.length) ?? 0) === 0) {
709466
+ if (removedCommentCarrierIds.has(atomId))
709467
+ return;
709468
+ if (removedTrackedCarrierIds.has(atomId)) {
709469
+ throw new Error(`left-boundary atom ${atomId} aliases a removed tracked carrier`);
709470
+ }
709471
+ leftBoundaryAtoms.push(atom);
709472
+ const anchorIdentity2 = rangeAnchorIdentityForAtom(atom);
709473
+ if (anchorIdentity2) {
709474
+ const matchingAnchors = getBlockAnchors(block).toArray().filter((anchor) => anchor.get(ANCHOR_KEY.id) === atom.get(ATOM_KEY.id) && anchor.get(ANCHOR_KEY.kind) === anchorIdentity2.kind && anchor.get(ANCHOR_KEY.boundary) === anchorIdentity2.boundary && Number(anchor.get(ANCHOR_KEY.visibleOffset) ?? -1) === span.startOffset);
709475
+ if (matchingAnchors.length !== 1) {
709476
+ throw new Error(`left-boundary atom ${atomId} does not resolve one exact anchor carrier`);
709477
+ }
709478
+ leftBoundaryAnchors.push(matchingAnchors[0]);
709479
+ }
709480
+ }
709481
+ });
709482
+ if (!reachedStart) {
709483
+ throw new Error(`tracked deletion start ${span.startAtomId} is not present in block ${span.blockId}`);
709484
+ }
709485
+ }
709486
+ }
709487
+ return {
709488
+ ok: true,
709489
+ plan: {
709490
+ doc: input.doc,
709491
+ decision: input.decision,
709492
+ removedCommentCarrierIds,
709493
+ resolved,
709494
+ work
709495
+ }
709496
+ };
709497
+ } catch (error50) {
709498
+ return { ok: false, failureReason: error50 instanceof Error ? error50.message : String(error50) };
709499
+ }
709500
+ }
709501
+ function liveTrackedTextSpanId(span) {
709502
+ return `${span.blockId}\x00${span.startAtomId}\x00${span.endAtomId}`;
709503
+ }
709504
+ function applyPreflightedTrackedTextDecisionsToStory(input) {
709505
+ if (input.plan.work.length === 0)
709220
709506
  return { ok: true, applied: false, alreadyDecided: true };
709221
709507
  try {
709222
- resolveStoryShardScope(input.doc).doc.transact(() => {
709223
- const work = live.flatMap((entry) => entry.spans.map((span) => ({ target: entry.target, span })));
709224
- for (const { target, span } of work) {
709225
- const found = findBlockMap(input.doc, span.blockId)?.block;
709226
- if (!found)
709227
- throw new Error(`target block ${span.blockId} is not present in the story shard`);
709228
- removeTrackedTextCarriers(found, span, input.decision === "reject" && target.carrierKind === "field-insertion");
709229
- removeCommentCarriersByIds(found, input.removedCommentIds);
709230
- }
709231
- const sorted = work.sort((left, right) => {
709508
+ resolveStoryShardScope(input.plan.doc).doc.transact(() => {
709509
+ const { decision, doc: doc2, removedCommentCarrierIds, resolved, work } = input.plan;
709510
+ for (const { target, span, block } of work) {
709511
+ removeTrackedTextCarriers(block, span, decision === "reject" && target.carrierKind === "field-insertion");
709512
+ removeCommentCarriersByIds(block, removedCommentCarrierIds);
709513
+ }
709514
+ const sorted = [...work].sort((left, right) => {
709232
709515
  if (left.span.blockId !== right.span.blockId)
709233
709516
  return left.span.blockId < right.span.blockId ? -1 : 1;
709234
709517
  if (left.span.startOffset !== right.span.startOffset)
709235
709518
  return right.span.startOffset - left.span.startOffset;
709236
- if (left.span.trackedKind === right.span.trackedKind)
709237
- return 0;
709519
+ if (left.span.trackedKind === right.span.trackedKind) {
709520
+ return right.span.startAtomIndex - left.span.startAtomIndex;
709521
+ }
709238
709522
  return left.span.trackedKind === "ins" ? -1 : 1;
709239
709523
  });
709240
- for (const { target, span } of sorted) {
709241
- if (span.trackedKind === "ins" && input.decision === "reject") {
709524
+ for (const { span, leftBoundaryAtoms, leftBoundaryAnchors } of sorted) {
709525
+ if (span.trackedKind === "ins" && decision === "reject") {
709242
709526
  const length3 = span.endOffset - span.startOffset;
709243
- if (length3 < 0) {
709244
- throw new Error(`tracked insert span ${target.rsid} is outside block ${span.blockId}`);
709245
- }
709246
709527
  if (length3 > 0) {
709247
709528
  deleteText2({
709248
- doc: input.doc,
709529
+ doc: doc2,
709249
709530
  blockId: span.blockId,
709250
709531
  offset: span.startOffset,
709251
709532
  length: length3,
@@ -709253,8 +709534,8 @@ function applyTrackedTextDecisionsToStory(input) {
709253
709534
  });
709254
709535
  }
709255
709536
  }
709256
- if (span.trackedKind === "del" && input.decision === "reject") {
709257
- const found = findBlockMap(input.doc, span.blockId)?.block;
709537
+ if (span.trackedKind === "del" && decision === "reject") {
709538
+ const found = findBlockMap(doc2, span.blockId)?.block;
709258
709539
  if (!found)
709259
709540
  throw new Error(`target block ${span.blockId} is not present in the story shard`);
709260
709541
  let offset = span.startOffset;
@@ -709266,7 +709547,7 @@ function applyTrackedTextDecisionsToStory(input) {
709266
709547
  continue;
709267
709548
  }
709268
709549
  insertText2({
709269
- doc: input.doc,
709550
+ doc: doc2,
709270
709551
  blockId: span.blockId,
709271
709552
  offset,
709272
709553
  text: segment.text,
@@ -709293,10 +709574,12 @@ function applyTrackedTextDecisionsToStory(input) {
709293
709574
  if (restoredBreakXml) {
709294
709575
  throw new Error(`attributed break restoration in ${span.blockId} is incomplete`);
709295
709576
  }
709577
+ leftBoundaryAtoms.forEach((atom) => atom.set(ATOM_KEY.visibleOffset, span.startOffset));
709578
+ leftBoundaryAnchors.forEach((anchor) => anchor.set(ANCHOR_KEY.visibleOffset, span.startOffset));
709296
709579
  }
709297
709580
  }
709298
709581
  for (const { target } of resolved) {
709299
- recordTrackedDecision(input.doc, target.rsid, input.decision, input.txId);
709582
+ recordTrackedDecision(doc2, target.rsid, decision, input.txId);
709300
709583
  }
709301
709584
  }, STORY_SURFACE_BACKEND_ORIGIN);
709302
709585
  } catch (error50) {
@@ -709375,6 +709658,7 @@ function collectLiveTrackedTextSpansInBlock(block, rsid, trackedKind) {
709375
709658
  blockId,
709376
709659
  startOffset: start.offset,
709377
709660
  endOffset: trackedKind === "del" ? start.offset + deletedTextLength2 : end.offset,
709661
+ startAtomIndex: start.index,
709378
709662
  startAtomId: start.id,
709379
709663
  endAtomId: end.id,
709380
709664
  preservedAtomIds: preservedForSpan.map((atom) => atom.id),
@@ -709386,24 +709670,38 @@ function collectLiveTrackedTextSpansInBlock(block, rsid, trackedKind) {
709386
709670
  return spans;
709387
709671
  }
709388
709672
  function removeTrackedTextCarriers(block, span, removeContainedAtoms) {
709389
- const ids = new Set([
709673
+ const ids = trackedTextCarrierIds(span, removeContainedAtoms);
709674
+ deleteYMapArrayEntriesByIds(getBlockAtoms(block), ATOM_KEY.id, ids);
709675
+ deleteYMapArrayEntriesByIds(getBlockAnchors(block), ANCHOR_KEY.id, ids);
709676
+ }
709677
+ function trackedTextCarrierIds(span, includeContainedAtoms) {
709678
+ return new Set([
709390
709679
  span.startAtomId,
709391
709680
  span.endAtomId,
709392
709681
  ...span.preservedAtomIds,
709393
- ...removeContainedAtoms ? span.containedAtomIds : []
709682
+ ...includeContainedAtoms ? span.containedAtomIds : []
709394
709683
  ]);
709395
- deleteYMapArrayEntriesByIds(getBlockAtoms(block), ATOM_KEY.id, ids);
709396
- deleteYMapArrayEntriesByIds(getBlockAnchors(block), ANCHOR_KEY.id, ids);
709397
709684
  }
709398
- function removeCommentCarriersByIds(block, commentIds) {
709399
- if (commentIds.size === 0)
709400
- return;
709685
+ function rangeAnchorIdentityForAtom(atom) {
709686
+ const atomKind = atom.get(ATOM_KEY.kind);
709687
+ if (atomKind !== INLINE_ATOM_KIND.commentMarker && atomKind !== INLINE_ATOM_KIND.bookmark)
709688
+ return null;
709689
+ const marker = String(asRecord6(atom.get(ATOM_KEY.meta)).marker ?? "");
709690
+ const boundary = marker.endsWith("End") ? "end" : marker.endsWith("Start") ? "start" : "point";
709691
+ return { kind: atomKind === INLINE_ATOM_KIND.commentMarker ? "comment" : "bookmark", boundary };
709692
+ }
709693
+ function collectCommentCarrierIds(commentIds) {
709401
709694
  const ids = new Set;
709402
709695
  for (const commentId of commentIds) {
709403
709696
  ids.add(`comment:${commentId}:start`);
709404
709697
  ids.add(`comment:${commentId}:end`);
709405
709698
  ids.add(`comment:${commentId}:ref`);
709406
709699
  }
709700
+ return ids;
709701
+ }
709702
+ function removeCommentCarriersByIds(block, ids) {
709703
+ if (ids.size === 0)
709704
+ return;
709407
709705
  deleteYMapArrayEntriesByIds(getBlockAtoms(block), ATOM_KEY.id, ids);
709408
709706
  deleteYMapArrayEntriesByIds(getBlockAnchors(block), ANCHOR_KEY.id, ids);
709409
709707
  }
@@ -709509,11 +709807,14 @@ function admitPreparedStorySurfaceMutation(prepared, options) {
709509
709807
  return options.session && extension2.family === "storyBlocks" && extension2.contentControlRewrite && storyIds.length === 1 ? { ok: true } : { ok: false, message: "Content-control rewrite requires a prepared structural delta and owning session." };
709510
709808
  }
709511
709809
  if (extension2.family === "storyTrackedDecide") {
709512
- if (usesTrackedDecisionSessionSync(prepared, extension2)) {
709810
+ if (usesTrackedDecisionSessionSync(prepared, extension2, options)) {
709513
709811
  return options.session ? { ok: true } : { ok: false, message: "tracked-change decision session sync requires an authoritative session" };
709514
709812
  }
709515
709813
  const trackedDecision = validatePreparedTrackedDecide(prepared, extension2, options);
709516
- return trackedDecision.ok ? { ok: true } : { ok: false, message: trackedDecision.failureReason };
709814
+ if (!trackedDecision.ok)
709815
+ return { ok: false, message: trackedDecision.failureReason };
709816
+ const preflight2 = preflightPreparedTrackedDecide({ prepared, ...trackedDecision });
709817
+ return preflight2.ok ? { ok: true, trackedDecision: preflight2.preflight } : { ok: false, message: preflight2.failureReason };
709517
709818
  }
709518
709819
  if (extension2.family === "storyTable") {
709519
709820
  if (extension2.historyReplay) {
@@ -709584,14 +709885,28 @@ function touchesOwnedStoryPart(prepared) {
709584
709885
  const touchedParts = new Set(prepared.resolvedTargets.partUris.map((partUri) => String(partUri)));
709585
709886
  return prepared.resolvedTargets.storyPartOwnership.some((entry) => touchedParts.has(String(entry.partUri)));
709586
709887
  }
709587
- function usesTrackedDecisionSessionSync(prepared, extension2) {
709888
+ function usesTrackedDecisionSessionSync(prepared, extension2, options) {
709588
709889
  if (extension2.historyReplay || extension2.sessionSync)
709589
709890
  return true;
709590
709891
  if (hasPreparedCommentRefRemaps(prepared))
709591
709892
  return true;
709592
- if (extension2.trackedTextDecide || prepared.source.kind !== "trackedChangeDecide")
709593
- return false;
709594
- return prepared.source.targets.some((target) => target.formatting !== undefined || target.structural !== undefined || target.derivedStructural !== undefined);
709893
+ if (!extension2.trackedTextDecide) {
709894
+ if (prepared.source.kind !== "trackedChangeDecide")
709895
+ return false;
709896
+ return prepared.source.targets.some((target) => target.formatting !== undefined || target.structural !== undefined || target.derivedStructural !== undefined);
709897
+ }
709898
+ return extension2.trackedTextDecide.targets.some((target) => {
709899
+ const doc2 = options.resolveStoryShard(target.storyId);
709900
+ if (!doc2)
709901
+ return false;
709902
+ const liveSpans = collectLiveTrackedTextSpans(doc2, target.rsid, target.trackedKind, [target.blockId]);
709903
+ if (liveSpans.length > 1)
709904
+ return true;
709905
+ if (!hasTrackedDecisionLedgerEntry(doc2, target.rsid) || findPreparedLiveTrackedTextSpans(doc2, target).length > 0) {
709906
+ return false;
709907
+ }
709908
+ return liveSpans.length > 0;
709909
+ });
709595
709910
  }
709596
709911
  function tryMirrorStoryText(prepared, extension2, options, admitted) {
709597
709912
  const validation = admitted ?? validateDirectStoryText(prepared, extension2, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/mcp",
3
- "version": "1.0.0-next.7",
3
+ "version": "1.0.0-next.9",
4
4
  "description": "Model Context Protocol server for SuperDoc, giving an AI agent tools to read and edit .docx files.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -24,13 +24,13 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@modelcontextprotocol/sdk": "^1.26.0",
27
- "@superdoc/sdk": "2.13.0-next.9",
27
+ "@superdoc/sdk": "2.13.0-next.11",
28
28
  "debug": "^4.4.3",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {
32
- "superdoc": "2.16.0-next.7",
33
- "@superdoc/docx-engine": "0.15.0-next.7",
32
+ "superdoc": "2.16.0-next.9",
33
+ "@superdoc/docx-engine": "0.15.0-next.9",
34
34
  "@superdoc/document-api": "0.1.0-alpha.0",
35
35
  "@types/bun": "^1.3.8",
36
36
  "@types/node": "22.19.2",