@youdie006/prodex 0.36.2 → 0.36.3

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.
@@ -3954,6 +3954,30 @@ export function transcriptMatchesSentPrompt(userText, sentPrompt) {
3954
3954
  // separates, U+E201 closes) and keeps the real sources in content_references.
3955
3955
  const CITATION_DELIMITER_PATTERN = /[\uE200-\uE206]/;
3956
3956
  const CITATION_MARKER_PATTERN = /\uE200[^\uE200-\uE206]*(?:[\uE202\uE204-\uE206][^\uE200-\uE206]*)*[\uE201\uE203]/g;
3957
+ /**
3958
+ * The words a citation marker was wrapping, if any.
3959
+ *
3960
+ * The markers come in two shapes. `<E200>cite<E202>turn0search0<E201>` is a
3961
+ * bare anchor - kind then reference id, nothing a reader would miss. But
3962
+ * `<E200>url<E202>Timeanddate.com — World Clock<E202>turn0search0<E201>` puts a
3963
+ * visible title in the middle, and that title is part of the sentence. Caught by
3964
+ * diffing a saved answer against its thread: a web-search reply ended at a
3965
+ * dangling "Source:" because the title had been deleted along with the marker.
3966
+ */
3967
+ export function citationMarkerText(marker) {
3968
+ // The first segment is the kind and the rest are a mix of reference tokens and,
3969
+ // for some kinds, prose. Sorting them by SHAPE rather than by kind keeps a
3970
+ // title from a kind not seen yet: `cite` can carry several ids
3971
+ // (`cite<E202>turn0search19<E202>turn0search5`), and dropping every
3972
+ // id-shaped segment leaves nothing there, which is right.
3973
+ const referenceToken = /^turn\d+[a-z]+\d+$/i;
3974
+ const segments = marker.replace(/^[\uE200-\uE206]|[\uE200-\uE206]$/g, "").split(/[\uE200-\uE206]/);
3975
+ return segments
3976
+ .slice(1)
3977
+ .filter((segment) => segment.length > 0 && !referenceToken.test(segment))
3978
+ .join(" ")
3979
+ .trim();
3980
+ }
3957
3981
  /**
3958
3982
  * Turn those markers into ordinary markdown links, so a saved answer keeps the
3959
3983
  * sources instead of the private-use noise (or, as in the rendered DOM, nothing
@@ -3984,11 +4008,13 @@ export function resolveTranscriptCitations(text, references = []) {
3984
4008
  };
3985
4009
  let resolved = text;
3986
4010
  for (const [marker, reference] of byMarker) {
3987
- resolved = resolved.split(marker).join(linksFor(reference));
4011
+ // No items to link is not permission to delete the sentence: fall back to
4012
+ // whatever the marker was wrapping.
4013
+ resolved = resolved.split(marker).join(linksFor(reference) || citationMarkerText(marker));
3988
4014
  }
3989
- // Anything still delimited had no reference to restore: strip it so private-use
3990
- // characters never reach a receipt.
3991
- return resolved.replace(CITATION_MARKER_PATTERN, "");
4015
+ // Anything still delimited had no reference to restore. The delimiters must not
4016
+ // reach a receipt, but the words between them still belong to the answer.
4017
+ return resolved.replace(CITATION_MARKER_PATTERN, (marker) => citationMarkerText(marker));
3992
4018
  }
3993
4019
  export function deepResearchReportExpression(conversationId) {
3994
4020
  return `(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.36.2",
3
+ "version": "0.36.3",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",