authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-10-14 04:47:28-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-14 04:47:28-07:00
log0796638512d4c610469905adc597cc33c307fc32
treeeeb0e19a37b3493062b1be82989a86b4b533ba74
parent7c43155883dcfe67340611ec68719aaeb3a5f8ff
signature Signed by PGP key B5690EEEBB952194

fix(chat): Codex follow-up suggestions show their label instead of raw markup (#19401)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

6 files changed, 122 insertions(+), 18 deletions(-)

apps/mobile/src/features/threads/ThreadFeed.tsx+2-2
...@@ -55,7 +55,7 @@ import {...@@ -55,7 +55,7 @@ import {
55} from "@t3tools/client-runtime/markdown-images";55} from "@t3tools/client-runtime/markdown-images";
56import { resolveViewedImageAsset } from "@t3tools/client-runtime/work-log/presentation";56import { resolveViewedImageAsset } from "@t3tools/client-runtime/work-log/presentation";
57import {57import {
58 renderCodexFileCitationsAsMarkdown,58 renderCodexInlineDirectivesAsMarkdown,
59 splitCodexArtifactTemplateMarkdown,59 splitCodexArtifactTemplateMarkdown,
60} from "@t3tools/shared/codexMarkdownDirectives";60} from "@t3tools/shared/codexMarkdownDirectives";
61import { CHAT_LIST_ANCHOR_OFFSET, resolveChatListAnchoredEndSpace } from "@t3tools/shared/chatList";61import { CHAT_LIST_ANCHOR_OFFSET, resolveChatListAnchoredEndSpace } from "@t3tools/shared/chatList";
...@@ -837,7 +837,7 @@ const AssistantMarkdownContent = memo(function AssistantMarkdownContent(props: {...@@ -837,7 +837,7 @@ const AssistantMarkdownContent = memo(function AssistantMarkdownContent(props: {
837 }837 }
838 if (segment.markdown.trim().length === 0) return null;838 if (segment.markdown.trim().length === 0) return null;
839839
840 const markdown = renderCodexFileCitationsAsMarkdown(segment.markdown);840 const markdown = renderCodexInlineDirectivesAsMarkdown(segment.markdown);
841 return (841 return (
842 <SelectableMarkdownText842 <SelectableMarkdownText
843 key={`markdown:${segment.sourceOffset}`}843 key={`markdown:${segment.sourceOffset}`}
apps/web/src/components/ChatMarkdown.test.tsx+30
...@@ -791,6 +791,36 @@ describe("ChatMarkdown file option chips", () => {...@@ -791,6 +791,36 @@ describe("ChatMarkdown file option chips", () => {
791 });791 });
792});792});
793793
794describe("ChatMarkdown Codex follow-up suggestions", () => {
795 it.each([true, false])("renders the label with parseRawHtml=%s", (parseRawHtml) => {
796 const html = renderToStaticMarkup(
797 <ChatMarkdown
798 cwd="/tmp/project"
799 text={
800 'Done.\n\n- :codex-followup[Prepare print version]{prompt="Prepare the document for printing."}'
801 }
802 lineBreaks={!parseRawHtml}
803 parseRawHtml={parseRawHtml}
804 />,
805 );
806
807 expect(html).toContain("Prepare print version");
808 expect(html).not.toContain("codex-followup");
809 expect(html).not.toContain("Prepare the document for printing.");
810 });
811
812 it("keeps the label of a malformed follow-up created by over-indented list recovery", () => {
813 const html = renderToStaticMarkup(
814 <ChatMarkdown
815 cwd="/tmp/project"
816 text="- Next :codex-followup[Prepare print version]"
817 />,
818 );
819
820 expect(html).toContain("<li>Next :codex-followup[Prepare print version]</li>");
821 });
822});
823
794const ARTIFACT_TEMPLATE_DIRECTIVE =824const ARTIFACT_TEMPLATE_DIRECTIVE =
795 '::artifact-template{skill_name="artifact-template-hello-world" skill_directory="/Users/test/.codex/skills/artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}';825 '::artifact-template{skill_name="artifact-template-hello-world" skill_directory="/Users/test/.codex/skills/artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}';
796826
apps/web/src/components/ChatMarkdown.tsx+2-2
...@@ -110,7 +110,7 @@ import { MarkdownThreadLink } from "./chat/MarkdownThreadLink";...@@ -110,7 +110,7 @@ import { MarkdownThreadLink } from "./chat/MarkdownThreadLink";
110import { isWindowsAbsolutePath } from "@t3tools/shared/path";110import { isWindowsAbsolutePath } from "@t3tools/shared/path";
111import {111import {
112 artifactTemplateFromHastProperties,112 artifactTemplateFromHastProperties,
113 renderCodexFileCitationsAsMarkdown,113 renderCodexInlineDirectivesAsMarkdown,
114} from "@t3tools/shared/codexMarkdownDirectives";114} from "@t3tools/shared/codexMarkdownDirectives";
115import { renderSkillInlineMarkdownChildren } from "./chat/SkillInlineText";115import { renderSkillInlineMarkdownChildren } from "./chat/SkillInlineText";
116import {116import {
...@@ -2453,7 +2453,7 @@ function useChatMarkdownState({...@@ -2453,7 +2453,7 @@ function useChatMarkdownState({
2453 string,2453 string,
2454 NonNullable<ReturnType<typeof resolveMarkdownFileLinkMeta>>2454 NonNullable<ReturnType<typeof resolveMarkdownFileLinkMeta>>
2455 >();2455 >();
2456 for (const href of extractMarkdownLinkHrefs(renderCodexFileCitationsAsMarkdown(text))) {2456 for (const href of extractMarkdownLinkHrefs(renderCodexInlineDirectivesAsMarkdown(text))) {
2457 if (parseComposerContextHref(href)) continue;2457 if (parseComposerContextHref(href)) continue;
2458 const normalizedHref = normalizeMarkdownLinkHrefKey(href);2458 const normalizedHref = normalizeMarkdownLinkHrefKey(href);
2459 if (metaByHref.has(normalizedHref)) continue;2459 if (metaByHref.has(normalizedHref)) continue;
packages/shared/src/codexMarkdownDirectives.test.ts+40-6
...@@ -5,7 +5,7 @@ import { unified } from "unified";...@@ -5,7 +5,7 @@ import { unified } from "unified";
5import {5import {
6 remarkCodexDirectives,6 remarkCodexDirectives,
7 renderCodexDirectivesForCopy,7 renderCodexDirectivesForCopy,
8 renderCodexFileCitationsAsMarkdown,8 renderCodexInlineDirectivesAsMarkdown,
9 splitCodexArtifactTemplateMarkdown,9 splitCodexArtifactTemplateMarkdown,
10} from "./codexMarkdownDirectives.ts";10} from "./codexMarkdownDirectives.ts";
11import { parseMarkdownFileLink } from "./markdownLinks.ts";11import { parseMarkdownFileLink } from "./markdownLinks.ts";
...@@ -26,6 +26,8 @@ interface TestNode {...@@ -26,6 +26,8 @@ interface TestNode {
26}26}
2727
28const FILE_CITATION = ':codex-file-citation{path="outputs/report.xlsx" purpose="output"}';28const FILE_CITATION = ':codex-file-citation{path="outputs/report.xlsx" purpose="output"}';
29const FOLLOW_UP =
30 ':codex-followup[Prepare print version]{prompt="Prepare the document for printing."}';
29const ARTIFACT_TEMPLATE =31const ARTIFACT_TEMPLATE =
30 '::artifact-template{skill_name="artifact-template-hello-world" skill_directory="/Users/test/.codex/skills/artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}';32 '::artifact-template{skill_name="artifact-template-hello-world" skill_directory="/Users/test/.codex/skills/artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}';
3133
...@@ -70,6 +72,15 @@ describe("remarkCodexDirectives", () => {...@@ -70,6 +72,15 @@ describe("remarkCodexDirectives", () => {
70 });72 });
71 });73 });
7274
75 it("renders a follow-up suggestion as its label", () => {
76 const followUp = parse(`Next: ${FOLLOW_UP}`).children?.[0]?.children?.[1];
77
78 expect(followUp).toMatchObject({
79 data: { hName: "span" },
80 children: [{ type: "text", value: "Prepare print version" }],
81 });
82 });
83
73 it.each([84 it.each([
74 "Meeting at 10:30",85 "Meeting at 10:30",
75 "Open src/main.ts:42",86 "Open src/main.ts:42",
...@@ -78,6 +89,7 @@ describe("remarkCodexDirectives", () => {...@@ -78,6 +89,7 @@ describe("remarkCodexDirectives", () => {
78 ":::note\ncontent\n:::",89 ":::note\ncontent\n:::",
79 ':codex-file-citation-extra{path="outputs/report.xlsx"}',90 ':codex-file-citation-extra{path="outputs/report.xlsx"}',
80 "::artifact-template-extra",91 "::artifact-template-extra",
92 ':codex-followup-extra[Label]{prompt="Prompt"}',
81 ])("does not change unrelated colon syntax: %s", (markdown) => {93 ])("does not change unrelated colon syntax: %s", (markdown) => {
82 expect(parse(markdown)).toEqual(parseOrdinaryMarkdown(markdown));94 expect(parse(markdown)).toEqual(parseOrdinaryMarkdown(markdown));
83 });95 });
...@@ -85,6 +97,9 @@ describe("remarkCodexDirectives", () => {...@@ -85,6 +97,9 @@ describe("remarkCodexDirectives", () => {
85 it.each([97 it.each([
86 ':codex-file-citation{purpose="output"}',98 ':codex-file-citation{purpose="output"}',
87 '::artifact-template{skill_name="artifact-template-hello-world"}',99 '::artifact-template{skill_name="artifact-template-hello-world"}',
100 ":codex-followup[Prepare print version]",
101 ':codex-followup[ ]{prompt="Prepare the document for printing."}',
102 ':codex-followup[Prepare print version]{prompt=" "}',
88 ])("keeps malformed supported directives literal: %s", (markdown) => {103 ])("keeps malformed supported directives literal: %s", (markdown) => {
89 expect(parse(markdown)).toEqual(parseOrdinaryMarkdown(markdown));104 expect(parse(markdown)).toEqual(parseOrdinaryMarkdown(markdown));
90 });105 });
...@@ -92,7 +107,7 @@ describe("remarkCodexDirectives", () => {...@@ -92,7 +107,7 @@ describe("remarkCodexDirectives", () => {
92107
93describe.each([108describe.each([
94 { name: "renderCodexDirectivesForCopy", render: renderCodexDirectivesForCopy },109 { name: "renderCodexDirectivesForCopy", render: renderCodexDirectivesForCopy },
95 { name: "renderCodexFileCitationsAsMarkdown", render: renderCodexFileCitationsAsMarkdown },110 { name: "renderCodexInlineDirectivesAsMarkdown", render: renderCodexInlineDirectivesAsMarkdown },
96])("$name file citation round trips", ({ render }) => {111])("$name file citation round trips", ({ render }) => {
97 it.each([112 it.each([
98 "C:\\Users\\test\\[draft]\\report.md",113 "C:\\Users\\test\\[draft]\\report.md",
...@@ -110,18 +125,33 @@ describe.each([...@@ -110,18 +125,33 @@ describe.each([
110125
111describe("native Markdown adapters", () => {126describe("native Markdown adapters", () => {
112 it("uses the same parser to render file citations as portable links", () => {127 it("uses the same parser to render file citations as portable links", () => {
113 expect(renderCodexFileCitationsAsMarkdown(`Created ${FILE_CITATION}.`)).toBe(128 expect(renderCodexInlineDirectivesAsMarkdown(`Created ${FILE_CITATION}.`)).toBe(
114 "Created [report.xlsx](<outputs/report.xlsx>).",129 "Created [report.xlsx](<outputs/report.xlsx>).",
115 );130 );
116 });131 });
117132
133 it("renders follow-up suggestions as their label Markdown", () => {
134 expect(
135 renderCodexInlineDirectivesAsMarkdown(
136 `- ${FOLLOW_UP}\n- :codex-followup[Review **document**]{prompt="Review it."}`,
137 ),
138 ).toBe("- Prepare print version\n- Review **document**");
139 });
140
141 it.each([`\\${FOLLOW_UP}`, `\`${FOLLOW_UP}\``])(
142 "does not render excluded follow-up syntax: %s",
143 (markdown) => {
144 expect(renderCodexInlineDirectivesAsMarkdown(markdown)).toBe(markdown);
145 },
146 );
147
118 it.each([148 it.each([
119 `\\${FILE_CITATION}`,149 `\\${FILE_CITATION}`,
120 `\`${FILE_CITATION}\``,150 `\`${FILE_CITATION}\``,
121 `\`\`\`text\n${FILE_CITATION}\n\`\`\``,151 `\`\`\`text\n${FILE_CITATION}\n\`\`\``,
122 `[See ${FILE_CITATION}](https://example.com)`,152 `[See ${FILE_CITATION}](https://example.com)`,
123 ])("does not render excluded citation syntax: %s", (markdown) => {153 ])("does not render excluded citation syntax: %s", (markdown) => {
124 expect(renderCodexFileCitationsAsMarkdown(markdown)).toBe(markdown);154 expect(renderCodexInlineDirectivesAsMarkdown(markdown)).toBe(markdown);
125 });155 });
126156
127 it("splits artifact cards from surrounding native Markdown", () => {157 it("splits artifact cards from surrounding native Markdown", () => {
...@@ -159,8 +189,12 @@ describe("native Markdown adapters", () => {...@@ -159,8 +189,12 @@ describe("native Markdown adapters", () => {
159189
160describe("directive copy adapter", () => {190describe("directive copy adapter", () => {
161 it("copies the Markdown representations shown by citation chips and template cards", () => {191 it("copies the Markdown representations shown by citation chips and template cards", () => {
162 expect(renderCodexDirectivesForCopy(`Created ${FILE_CITATION}.\n\n${ARTIFACT_TEMPLATE}`)).toBe(192 expect(
163 "Created [report.xlsx](<outputs/report.xlsx>).\n\nHello World (Document template)",193 renderCodexDirectivesForCopy(
194 `Created ${FILE_CITATION}.\n\n${ARTIFACT_TEMPLATE}\n\n- ${FOLLOW_UP}`,
195 ),
196 ).toBe(
197 "Created [report.xlsx](<outputs/report.xlsx>).\n\nHello World (Document template)\n\n- Prepare print version",
164 );198 );
165 });199 });
166200
packages/shared/src/codexMarkdownDirectives.ts+46-6
...@@ -20,6 +20,7 @@ const COLON = 58;...@@ -20,6 +20,7 @@ const COLON = 58;
20const DASH = 45;20const DASH = 45;
21const UNDERSCORE = 95;21const UNDERSCORE = 95;
22const CODEX_FILE_CITATION_NAME = "codex-file-citation";22const CODEX_FILE_CITATION_NAME = "codex-file-citation";
23const CODEX_FOLLOW_UP_NAME = "codex-followup";
23const CODEX_ARTIFACT_TEMPLATE_NAME = "artifact-template";24const CODEX_ARTIFACT_TEMPLATE_NAME = "artifact-template";
2425
25export const CODEX_ARTIFACT_TEMPLATE_HAST_PROPERTIES = [26export const CODEX_ARTIFACT_TEMPLATE_HAST_PROPERTIES = [
...@@ -46,6 +47,7 @@ interface MarkdownAstNode {...@@ -46,6 +47,7 @@ interface MarkdownAstNode {
46 data?: {47 data?: {
47 codexArtifactTemplate?: CodexArtifactTemplate;48 codexArtifactTemplate?: CodexArtifactTemplate;
48 codexFileCitationMarkdown?: string;49 codexFileCitationMarkdown?: string;
50 codexFollowUp?: true;
49 hName?: string;51 hName?: string;
50 hProperties?: Record<string, unknown>;52 hProperties?: Record<string, unknown>;
51 };53 };
...@@ -132,7 +134,10 @@ function codexDirectiveSyntax(): Extension {...@@ -132,7 +134,10 @@ function codexDirectiveSyntax(): Extension {
132134
133 return {135 return {
134 text: {136 text: {
135 [COLON]: restrictedDirective(textDirective, 1, CODEX_FILE_CITATION_NAME),137 [COLON]: [
138 restrictedDirective(textDirective, 1, CODEX_FILE_CITATION_NAME),
139 restrictedDirective(textDirective, 1, CODEX_FOLLOW_UP_NAME),
140 ],
136 },141 },
137 flow: {142 flow: {
138 [COLON]: restrictedDirective(leafDirective, 2, CODEX_ARTIFACT_TEMPLATE_NAME),143 [COLON]: restrictedDirective(leafDirective, 2, CODEX_ARTIFACT_TEMPLATE_NAME),
...@@ -149,15 +154,20 @@ function sourceForNode(node: MarkdownAstNode, source: string): string {...@@ -149,15 +154,20 @@ function sourceForNode(node: MarkdownAstNode, source: string): string {
149 return start === undefined || end === undefined ? "" : source.slice(start, end);154 return start === undefined || end === undefined ? "" : source.slice(start, end);
150}155}
151156
157function textContent(node: MarkdownAstNode): string {
158 return node.value ?? (node.children ?? []).map(textContent).join("");
159}
160
152function sourceForDirective(node: MarkdownAstNode, source: string, marker: ":" | "::"): string {161function sourceForDirective(node: MarkdownAstNode, source: string, marker: ":" | "::"): string {
153 const prefix = `${marker}${node.name ?? ""}`;162 const prefix = `${marker}${node.name ?? ""}`;
154 const slicedSource = sourceForNode(node, source);163 const slicedSource = sourceForNode(node, source);
155 if (slicedSource.startsWith(prefix)) return slicedSource;164 if (slicedSource.startsWith(prefix)) return slicedSource;
156165
166 const label = node.children?.length ? `[${textContent(node)}]` : "";
157 const attributes = Object.entries(node.attributes ?? {}).map(([name, value]) =>167 const attributes = Object.entries(node.attributes ?? {}).map(([name, value]) =>
158 value === null ? name : `${name}=${JSON.stringify(value)}`,168 value === null ? name : `${name}=${JSON.stringify(value)}`,
159 );169 );
160 return `${prefix}${attributes.length === 0 ? "" : `{${attributes.join(" ")}}`}`;170 return `${prefix}${label}${attributes.length === 0 ? "" : `{${attributes.join(" ")}}`}`;
161}171}
162172
163function restoreTextDirective(node: MarkdownAstNode, source: string): void {173function restoreTextDirective(node: MarkdownAstNode, source: string): void {
...@@ -199,6 +209,18 @@ function renderFileCitation(node: MarkdownAstNode, source: string, insideLink: b...@@ -199,6 +209,18 @@ function renderFileCitation(node: MarkdownAstNode, source: string, insideLink: b
199 delete node.value;209 delete node.value;
200}210}
201211
212/** Shows only the label. T3 has no suggestion UI, so the hidden prompt is not shown. */
213function renderFollowUp(node: MarkdownAstNode, source: string): void {
214 if (!node.attributes?.prompt?.trim() || !textContent(node).trim()) {
215 restoreTextDirective(node, source);
216 return;
217 }
218
219 node.data = { codexFollowUp: true, hName: "span" };
220 delete node.name;
221 delete node.attributes;
222}
223
202function renderArtifactTemplate(node: MarkdownAstNode, source: string): void {224function renderArtifactTemplate(node: MarkdownAstNode, source: string): void {
203 const template = resolveCodexArtifactTemplate(node.attributes);225 const template = resolveCodexArtifactTemplate(node.attributes);
204 if (!template) {226 if (!template) {
...@@ -231,6 +253,9 @@ function transformCodexDirectives(node: MarkdownAstNode, source: string, insideL...@@ -231,6 +253,9 @@ function transformCodexDirectives(node: MarkdownAstNode, source: string, insideL
231 renderFileCitation(node, source, insideLink);253 renderFileCitation(node, source, insideLink);
232 return;254 return;
233 }255 }
256 if (node.type === "textDirective" && node.name === CODEX_FOLLOW_UP_NAME) {
257 renderFollowUp(node, source);
258 }
234 if (node.type === "leafDirective" && node.name === CODEX_ARTIFACT_TEMPLATE_NAME) {259 if (node.type === "leafDirective" && node.name === CODEX_ARTIFACT_TEMPLATE_NAME) {
235 renderArtifactTemplate(node, source);260 renderArtifactTemplate(node, source);
236 return;261 return;
...@@ -242,7 +267,7 @@ function transformCodexDirectives(node: MarkdownAstNode, source: string, insideL...@@ -242,7 +267,7 @@ function transformCodexDirectives(node: MarkdownAstNode, source: string, insideL
242 }267 }
243}268}
244269
245/** Adds grammar only for the two directives emitted by Codex, then renders them as mdast. */270/** Adds grammar only for the directives emitted by Codex, then renders them as mdast. */
246function attachCodexDirectives(this: Processor) {271function attachCodexDirectives(this: Processor) {
247 const data = this.data();272 const data = this.data();
248 const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = []);273 const micromarkExtensions = data.micromarkExtensions ?? (data.micromarkExtensions = []);
...@@ -270,6 +295,7 @@ interface DirectiveMatch {...@@ -270,6 +295,7 @@ interface DirectiveMatch {
270 readonly end: number;295 readonly end: number;
271 readonly markdown?: string;296 readonly markdown?: string;
272 readonly template?: CodexArtifactTemplate;297 readonly template?: CodexArtifactTemplate;
298 readonly labelRange?: readonly [start: number, end: number];
273}299}
274300
275function collectDirectiveMatches(node: MarkdownAstNode, matches: DirectiveMatch[]): void {301function collectDirectiveMatches(node: MarkdownAstNode, matches: DirectiveMatch[]): void {
...@@ -284,6 +310,12 @@ function collectDirectiveMatches(node: MarkdownAstNode, matches: DirectiveMatch[...@@ -284,6 +310,12 @@ function collectDirectiveMatches(node: MarkdownAstNode, matches: DirectiveMatch[
284 matches.push({ start, end, template: node.data.codexArtifactTemplate });310 matches.push({ start, end, template: node.data.codexArtifactTemplate });
285 return;311 return;
286 }312 }
313 const labelStart = node.children?.[0]?.position?.start.offset;
314 const labelEnd = node.children?.at(-1)?.position?.end.offset;
315 if (node.data?.codexFollowUp && labelStart !== undefined && labelEnd !== undefined) {
316 matches.push({ start, end, labelRange: [labelStart, labelEnd] });
317 return;
318 }
287 }319 }
288 for (const child of node.children ?? []) collectDirectiveMatches(child, matches);320 for (const child of node.children ?? []) collectDirectiveMatches(child, matches);
289}321}
...@@ -296,7 +328,9 @@ function renderDirectiveMatches(...@@ -296,7 +328,9 @@ function renderDirectiveMatches(
296 collectDirectiveMatches(parseCodexMarkdown(markdown), matches);328 collectDirectiveMatches(parseCodexMarkdown(markdown), matches);
297 let rendered = markdown;329 let rendered = markdown;
298 for (const match of matches.sort((left, right) => right.start - left.start)) {330 for (const match of matches.sort((left, right) => right.start - left.start)) {
299 const replacement = replacementFor(match);331 const replacement = match.labelRange
332 ? markdown.slice(...match.labelRange)
333 : replacementFor(match);
300 if (replacement !== undefined) {334 if (replacement !== undefined) {
301 rendered = rendered.slice(0, match.start) + replacement + rendered.slice(match.end);335 rendered = rendered.slice(0, match.start) + replacement + rendered.slice(match.end);
302 }336 }
...@@ -305,8 +339,13 @@ function renderDirectiveMatches(...@@ -305,8 +339,13 @@ function renderDirectiveMatches(
305}339}
306340
307/** Native Markdown renderers use this adapter because they cannot consume a Remark tree. */341/** Native Markdown renderers use this adapter because they cannot consume a Remark tree. */
308export function renderCodexFileCitationsAsMarkdown(markdown: string): string {342export function renderCodexInlineDirectivesAsMarkdown(markdown: string): string {
309 if (!markdown.includes(`:${CODEX_FILE_CITATION_NAME}`)) return markdown;343 if (
344 !markdown.includes(`:${CODEX_FILE_CITATION_NAME}`) &&
345 !markdown.includes(`:${CODEX_FOLLOW_UP_NAME}`)
346 ) {
347 return markdown;
348 }
310349
311 return renderDirectiveMatches(markdown, (match) => match.markdown);350 return renderDirectiveMatches(markdown, (match) => match.markdown);
312}351}
...@@ -315,6 +354,7 @@ export function renderCodexFileCitationsAsMarkdown(markdown: string): string {...@@ -315,6 +354,7 @@ export function renderCodexFileCitationsAsMarkdown(markdown: string): string {
315export function renderCodexDirectivesForCopy(markdown: string): string {354export function renderCodexDirectivesForCopy(markdown: string): string {
316 if (355 if (
317 !markdown.includes(`:${CODEX_FILE_CITATION_NAME}`) &&356 !markdown.includes(`:${CODEX_FILE_CITATION_NAME}`) &&
357 !markdown.includes(`:${CODEX_FOLLOW_UP_NAME}`) &&
318 !markdown.includes(`::${CODEX_ARTIFACT_TEMPLATE_NAME}`)358 !markdown.includes(`::${CODEX_ARTIFACT_TEMPLATE_NAME}`)
319 ) {359 ) {
320 return markdown;360 return markdown;
packages/shared/src/threadFindText.ts+2-2
...@@ -5,7 +5,7 @@ import {...@@ -5,7 +5,7 @@ import {
5} from "./inlineSkills.ts";5} from "./inlineSkills.ts";
6import {6import {
7 artifactTemplateFromHastProperties,7 artifactTemplateFromHastProperties,
8 renderCodexFileCitationsAsMarkdown,8 renderCodexInlineDirectivesAsMarkdown,
9} from "./codexMarkdownDirectives.ts";9} from "./codexMarkdownDirectives.ts";
10import { codexArtifactTemplatePresentationLabel } from "./codexArtifactTemplates.ts";10import { codexArtifactTemplatePresentationLabel } from "./codexArtifactTemplates.ts";
11import {11import {
...@@ -117,7 +117,7 @@ function markdownThreadFindText(...@@ -117,7 +117,7 @@ function markdownThreadFindText(
117 : assistantProcessor;117 : assistantProcessor;
118 const tree = processor.runSync(processor.parse(markdown));118 const tree = processor.runSync(processor.parse(markdown));
119 const paths = [119 const paths = [
120 ...extractMarkdownLinkHrefs(renderCodexFileCitationsAsMarkdown(markdown)),120 ...extractMarkdownLinkHrefs(renderCodexInlineDirectivesAsMarkdown(markdown)),
121 ...extractInlineCodeSpans(markdown).flatMap((span) => inlineCodeFilePathCandidate(span) ?? []),121 ...extractInlineCodeSpans(markdown).flatMap((span) => inlineCodeFilePathCandidate(span) ?? []),
122 ].flatMap((href) => {122 ].flatMap((href) => {
123 const target = resolveMarkdownFileLinkTarget(href, cwd);123 const target = resolveMarkdownFileLinkTarget(href, cwd);