Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 13 additions & 23 deletions apps/discord-bot/src/features/ResponseBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
buildStreamHistoryMarkdownText,
DISCORD_MAX_FILES_PER_MESSAGE,
imageAttachmentsOf,
shouldAttachT3DeepLink,
STREAM_HISTORY_MARKDOWN_NAME,
streamHistoryHasAdditionalContent,
unpostedAttachments,
Expand Down Expand Up @@ -67,7 +66,6 @@ import {
stripMarkdownImages,
type MarkdownImageRef,
} from "../presentation/markdownImages.ts";
import { hasMarkdownTables } from "../presentation/asciiTables.ts";
import {
chunkDiscordContent,
formatInProgressChunk,
Expand Down Expand Up @@ -3077,7 +3075,7 @@ export const runBridge = (
*
* On turn complete: stream messages are deleted, archived as stream-history.md,
* and the final answer is posted as Discord markdown (links live; no ASCII tables),
* with a T3 deep link when the answer is long or has GFM tables.
* with a T3 deep link always on the stats footer.
*/
const postOrEditAssistantUnlocked = (args: {
readonly turnId: string | null;
Expand Down Expand Up @@ -3487,7 +3485,7 @@ export const runBridge = (
/**
* Final delivery for a completed assistant turn:
* 1. Post Discord markdown content (chunked if needed; links stay clickable)
* 2. Append · [T3](…#message-…) when multi-chunk or tables (full render in Omegent)
* 2. Always append · [T3](…#message-…) on the stats footer
* 3. Attach stream-history.md + chat/local images as files
* 4. Delete the in-progress stream messages so only the final answer remains visible
*/
Expand Down Expand Up @@ -3698,8 +3696,8 @@ export const runBridge = (

// Final channel text: strip image embeds but keep readable local file references.
// Never leave Working.. or the stream placeholder.
// Keep Discord markdown as-is (links stay clickable). Do not ASCII-ify tables
// long / table-heavy answers get a T3 deep link for full rendering in Omegent.
// Keep Discord markdown as-is (links stay clickable). Do not ASCII-ify tables.
// Stats footer always gets · [T3](deep link) when the web UI base is configured.
const finalText = rewriteMarkdownLocalFileLinksForDiscord({
text: stripWorkingIndicator(stripMarkdownImages(text)),
githubUrlsBySrc,
Expand Down Expand Up @@ -3735,7 +3733,8 @@ export const runBridge = (
? ["_(done)_"]
: [];

// Small italic turn stats on the final answer (model / effort / duration / tokens).
// Small italic turn stats on the final answer (model / effort / duration / tokens),
// then always append · [T3](deep link) on that footer section when the URL is known.
const statsThread = yield* Ref.get(latestThreadRef);
const statsLine = formatTurnResponseStatsLine({
modelSelection: statsThread?.modelSelection ?? null,
Expand All @@ -3745,22 +3744,13 @@ export const runBridge = (
});
let finalChunks = appendStatsToMessageChunks(baseFinalChunks, statsLine, DISCORD_LIMIT);

// Long multi-message finals and any answer with GFM tables → · [T3](deep link).
if (
shouldAttachT3DeepLink({
text: renderedFinalText,
hasMarkdownTables: hasMarkdownTables(renderedFinalText),
messageChunkCount: finalChunks.length,
})
) {
const botConfig = yield* DiscordBotConfig;
const t3Url = buildOmegentThreadMessageUrl({
webUiBaseUrl: botConfig.webUiBaseUrl,
threadId: input.t3ThreadId,
messageId: t3MessageId,
});
finalChunks = appendT3DeepLinkToChunks(finalChunks, t3Url, DISCORD_LIMIT);
}
const botConfig = yield* DiscordBotConfig;
const t3Url = buildOmegentThreadMessageUrl({
webUiBaseUrl: botConfig.webUiBaseUrl,
threadId: input.t3ThreadId,
messageId: t3MessageId,
});
finalChunks = appendT3DeepLinkToChunks(finalChunks, t3Url, DISCORD_LIMIT);

if (finalChunks.length === 0 && files.length === 0) {
// Nothing useful to post — just clear any leftover Working.. stream messages.
Expand Down
36 changes: 8 additions & 28 deletions apps/discord-bot/src/presentation/attachments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
attachmentKey,
buildStreamHistoryMarkdownText,
imageAttachmentsOf,
shouldAttachT3DeepLink,
STREAM_HISTORY_MARKDOWN_NAME,
streamHistoryHasAdditionalContent,
unpostedAttachments,
Expand Down Expand Up @@ -95,37 +94,18 @@ describe("buildStreamHistoryMarkdownText", () => {
});

describe("T3 deep link caption helpers", () => {
it("appends a short same-line T3 link", () => {
it("appends a short same-line T3 link on the stats footer", () => {
expect(
withT3DeepLink("Summary", "https://t3vm.tail86038f.ts.net/?thread=tid-1#message-msg-1"),
).toBe("Summary · [T3](https://t3vm.tail86038f.ts.net/?thread=tid-1#message-msg-1)");
withT3DeepLink(
"_`grok-4.5` · effort high · 50s_",
"https://t3vm.tail86038f.ts.net/?thread=tid-1#message-msg-1",
),
).toBe(
"_`grok-4.5` · effort high · 50s_ · [T3](https://t3vm.tail86038f.ts.net/?thread=tid-1#message-msg-1)",
);
expect(withT3DeepLink("Summary", null)).toBe("Summary");
});

it("links when the answer has tables or needs multiple chunks", () => {
expect(
shouldAttachT3DeepLink({
text: "short",
hasMarkdownTables: false,
messageChunkCount: 1,
}),
).toBe(false);
expect(
shouldAttachT3DeepLink({
text: "long",
hasMarkdownTables: false,
messageChunkCount: 2,
}),
).toBe(true);
expect(
shouldAttachT3DeepLink({
text: "| A | B |\n|---|---|\n| 1 | 2 |",
hasMarkdownTables: true,
messageChunkCount: 1,
}),
).toBe(true);
});

it("appends the link onto the last chunk without overflowing", () => {
const url = "https://t3vm.example/?thread=t#message-m";
expect(appendT3DeepLinkToChunks(["hello"], url, 2000)).toEqual([`hello · [T3](${url})`]);
Expand Down
19 changes: 3 additions & 16 deletions apps/discord-bot/src/presentation/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function buildStreamHistoryMarkdownFile(streamText: string): File | null
/**
* Append a compact T3 deep link on a Discord caption/chunk.
* Same-line ` · [T3](url)` — clickable Discord markdown, short label.
* Used on the stats footer of every final answer.
*/
export function withT3DeepLink(caption: string, t3Url: string | null | undefined): string {
const url = t3Url?.trim() ?? "";
Expand All @@ -79,22 +80,8 @@ export function withT3DeepLink(caption: string, t3Url: string | null | undefined
}

/**
* When the final answer would need multi-message chunking or contains GFM tables,
* surface a T3 deep link so the full rendered answer is one click away.
* Short single-message prose without tables stays link-free.
*/
export function shouldAttachT3DeepLink(input: {
readonly text: string;
readonly hasMarkdownTables: boolean;
readonly messageChunkCount: number;
}): boolean {
if (input.text.trim() === "") return false;
if (input.hasMarkdownTables) return true;
return input.messageChunkCount > 1;
}

/**
* Append a T3 deep link onto the last message chunk, respecting the Discord limit.
* Append a T3 deep link onto the last message chunk (the stats footer),
* respecting the Discord limit. Always used on finals when a URL is available.
* If the link would overflow the last chunk, emit it as its own trailing chunk.
*/
export function appendT3DeepLinkToChunks(
Expand Down
Loading