Skip to content

Commit 1b1fc20

Browse files
committed
refactor(auto-reply): distill verbose commentary lane wiring
1 parent 0bfdb4d commit 1b1fc20

5 files changed

Lines changed: 71 additions & 112 deletions

File tree

extensions/discord/src/monitor/message-handler.process.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,8 +1038,6 @@ async function processDiscordMessageInner(
10381038
},
10391039
onItemEvent: async (payload) => {
10401040
if (payload.kind === "preamble") {
1041-
// While the durable verbose commentary lane is active, the ephemeral
1042-
// draft yields its commentary lines so commentary renders once.
10431041
if (verboseProgressActive()) {
10441042
return;
10451043
}

extensions/telegram/src/bot-message-dispatch.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,10 +1028,9 @@ export const dispatchTelegramMessage = async ({
10281028
});
10291029
let finalAnswerDeliveryStarted = false;
10301030
let finalAnswerDelivered = false;
1031-
// While the durable verbose commentary lane is active, the ephemeral draft
1032-
// yields its commentary lines so commentary is not rendered in both lanes.
1033-
// Tool/plan lines keep the draft: they have no durable counterpart in
1034-
// streamed runs, so yielding them would lose information.
1031+
// While the durable verbose lane is active, the ephemeral draft yields its
1032+
// commentary lines so they render once. Tool/plan status lines keep the
1033+
// draft: they have no durable counterpart in streamed runs.
10351034
let verboseProgressActive: () => boolean = () => false;
10361035
const pushStreamToolProgress = async (
10371036
line?: string | ChannelProgressDraftLine,
@@ -2041,10 +2040,8 @@ export const dispatchTelegramMessage = async ({
20412040
}
20422041
if (segment.lane === "answer" && info.kind === "tool") {
20432042
if (verboseProgressActive()) {
2044-
// The durable verbose progress lane owns tool/commentary
2045-
// payloads for this run: deliver as a real standalone
2046-
// message instead of diverting into the streaming draft,
2047-
// which is ephemeral and discarded at final.
2043+
// Durable lane owns tool payloads: send standalone instead
2044+
// of diverting into the draft, which is discarded at final.
20482045
if (
20492046
await sendPayload(
20502047
applyTextToPayload(effectivePayload, segment.update.text),

src/auto-reply/reply/agent-runner-cli-dispatch.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,8 @@ function createToolEventBridge(params: {
224224
/**
225225
* Tracks CLI tool start/result events and renders the same durable tool
226226
* summaries the embedded runner emits: a formatToolAggregate line per result
227-
* (with args-derived meta captured at start), plus the tool output block when
228-
* full verbose output is enabled. The CLI parser emits tool result events, but
229-
* until now they were dropped at the bridge, so CLI-backed runs had no durable
230-
* tool record under verbose while embedded runs did.
227+
* (args-derived meta captured at start), plus the output block under full
228+
* verbose. Keeps CLI runs at tool-summary parity with embedded runs.
231229
*/
232230
export function createCliToolSummaryTracker(params: {
233231
detailMode?: "explain" | "raw";

src/auto-reply/reply/dispatch-from-config.ts

Lines changed: 39 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,13 +2143,9 @@ export async function dispatchReplyFromConfig(
21432143
return !reply.hasMedia && !hasExecApprovalPayload(payload);
21442144
};
21452145
// Durable inter-tool commentary lane: with verbose progress on, preamble
2146-
// items become standalone progress messages (like tool summaries) instead of
2147-
// living only in ephemeral channel drafts. The latest text per item id is
2148-
// buffered so producers that re-emit snapshots for the same item send one
2149-
// message; the buffer flushes when the producer moves on (a different item,
2150-
// a tool event, a block reply, or the final reply). The delivery helpers
2151-
// reference gates declared later in this scope; they only run once the
2152-
// resolver is executing, well after scope initialization completes.
2146+
// items become standalone progress messages like tool summaries. The latest
2147+
// text per item id is buffered (snapshot producers re-emit the same item)
2148+
// and flushed when the producer moves on, always before the final reply.
21532149
let pendingCommentaryProgress: { itemId?: string; text: string } | null = null;
21542150
const deliverCommentaryProgressMessage = async (text: string) => {
21552151
if (!shouldSendToolSummaries() || shouldSuppressProgressDelivery()) {
@@ -2217,8 +2213,7 @@ export async function dispatchReplyFromConfig(
22172213
}
22182214
};
22192215
throwIfFinalDeliveryAborted();
2220-
// Drain any trailing commentary before the final reply so the durable
2221-
// progress lane completes ahead of the answer.
2216+
// Trailing commentary must land ahead of the final answer.
22222217
await flushPendingCommentaryProgress();
22232218
throwIfFinalDeliveryAborted();
22242219
const sourceReplyTranscriptMirror =
@@ -2663,31 +2658,34 @@ export async function dispatchReplyFromConfig(
26632658
// classification in the CLI runners is wired once at run start, so a
26642659
// mid-run verbose toggle cannot move inter-tool commentary between lanes.
26652660
const deliverStandaloneCommentaryProgress = shouldEmitVerboseProgress();
2666-
const coreOwnedOnItemEvent = (() => {
2667-
const forwardItemEvent = wrapProgressCallback(params.replyOptions?.onItemEvent, {
2668-
forwardWhenSourceDeliverySuppressed: true,
2669-
requiresToolSummaryVisibility: true,
2670-
waitForDirectBlockReplyDelivery: true,
2671-
onForward: (payload) => {
2672-
if (hasFailedProgressStatus(payload)) {
2673-
markVisibleToolErrorProgress();
2674-
}
2675-
},
2676-
});
2677-
return async (payload: Parameters<NonNullable<GetReplyOptions["onItemEvent"]>>[0]) => {
2678-
if (isDispatchOperationAborted()) {
2679-
return;
2661+
const forwardItemEvent = wrapProgressCallback(params.replyOptions?.onItemEvent, {
2662+
forwardWhenSourceDeliverySuppressed: true,
2663+
requiresToolSummaryVisibility: true,
2664+
waitForDirectBlockReplyDelivery: true,
2665+
onForward: (payload) => {
2666+
if (hasFailedProgressStatus(payload)) {
2667+
markVisibleToolErrorProgress();
26802668
}
2681-
if (!forwardItemEvent) {
2682-
// The wrapped forwarder marks progress itself when present.
2683-
markProgress();
2684-
}
2685-
if (payload.kind === "preamble") {
2686-
await noteCommentaryProgress(payload);
2687-
}
2688-
await forwardItemEvent?.(payload);
2689-
};
2690-
})();
2669+
},
2670+
});
2671+
// Item-event presence gates CLI commentary classification downstream, so
2672+
// the handler exists exactly when verbose buffers it or a channel consumes it.
2673+
const onItemEvent =
2674+
deliverStandaloneCommentaryProgress || forwardItemEvent
2675+
? async (payload: Parameters<NonNullable<GetReplyOptions["onItemEvent"]>>[0]) => {
2676+
if (isDispatchOperationAborted()) {
2677+
return;
2678+
}
2679+
if (!forwardItemEvent) {
2680+
// The wrapped forwarder marks progress itself when present.
2681+
markProgress();
2682+
}
2683+
if (deliverStandaloneCommentaryProgress && payload.kind === "preamble") {
2684+
await noteCommentaryProgress(payload);
2685+
}
2686+
await forwardItemEvent?.(payload);
2687+
}
2688+
: undefined;
26912689
// Let draft-rendering channels yield their ephemeral commentary lines while
26922690
// the durable verbose commentary lane is delivering the same content.
26932691
params.replyOptions?.onVerboseProgressVisibility?.(
@@ -2732,26 +2730,13 @@ export async function dispatchReplyFromConfig(
27322730
requiresToolSummaryVisibility: true,
27332731
waitForDirectBlockReplyDelivery: true,
27342732
onForward: async () => {
2735-
// A tool is starting: the preceding commentary block is final, so
2736-
// flush it ahead of the tool's own progress rendering.
2733+
// Commentary precedes the tool that follows it.
27372734
await flushPendingCommentaryProgress();
27382735
},
27392736
}),
2740-
onItemEvent: deliverStandaloneCommentaryProgress
2741-
? coreOwnedOnItemEvent
2742-
: wrapProgressCallback(params.replyOptions?.onItemEvent, {
2743-
forwardWhenSourceDeliverySuppressed: true,
2744-
requiresToolSummaryVisibility: true,
2745-
waitForDirectBlockReplyDelivery: true,
2746-
onForward: (payload) => {
2747-
if (hasFailedProgressStatus(payload)) {
2748-
markVisibleToolErrorProgress();
2749-
}
2750-
},
2751-
}),
2752-
commentaryProgressEnabled: deliverStandaloneCommentaryProgress
2753-
? true
2754-
: params.replyOptions?.commentaryProgressEnabled,
2737+
onItemEvent,
2738+
commentaryProgressEnabled:
2739+
deliverStandaloneCommentaryProgress || params.replyOptions?.commentaryProgressEnabled,
27552740
onCommandOutput: wrapProgressCallback(params.replyOptions?.onCommandOutput, {
27562741
forwardWhenSourceDeliverySuppressed: true,
27572742
requiresToolSummaryVisibility: true,
@@ -2783,8 +2768,7 @@ export async function dispatchReplyFromConfig(
27832768
return;
27842769
}
27852770
markInboundDedupeReplayUnsafe();
2786-
// The tool finished: any buffered commentary preceded this tool
2787-
// call, so it must land before the tool summary.
2771+
// Buffered commentary preceded this tool; land it before the summary.
27882772
await flushPendingCommentaryProgress();
27892773
if (!suppressAutomaticSourceDelivery && shouldSendToolSummaries()) {
27902774
await onToolResultFromReplyOptions?.(payload);
@@ -2943,8 +2927,7 @@ export async function dispatchReplyFromConfig(
29432927
) {
29442928
markInboundDedupeReplayUnsafe();
29452929
}
2946-
// Assistant text is moving on: buffered commentary preceded this
2947-
// block, so deliver it first to keep the lanes in order.
2930+
// Buffered commentary preceded this block; deliver it first.
29482931
await flushPendingCommentaryProgress();
29492932
if (suppressDelivery) {
29502933
return;
@@ -3097,8 +3080,8 @@ export async function dispatchReplyFromConfig(
30973080
}
30983081

30993082
const replies = replyResult ? (Array.isArray(replyResult) ? replyResult : [replyResult]) : [];
3100-
// Backstop: a run can end without a visible final reply (silent or
3101-
// streaming-delivered turns); trailing commentary must still land.
3083+
// Backstop: silent/streaming-delivered turns end without a visible final
3084+
// reply; trailing commentary must still land.
31023085
await flushPendingCommentaryProgress();
31033086
const beforeAgentRunBlocked = replies.some(
31043087
(reply) => getReplyPayloadMetadata(reply)?.beforeAgentRunBlocked === true,

src/auto-reply/reply/followup-runner.ts

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,29 @@ export function createFollowupRunner(params: {
827827
const notifyUserMessagePersisted = () => {
828828
queuedUserMessagePersistedAcrossFallback = true;
829829
};
830+
// Shared by the embedded onToolResult callback and the CLI tool
831+
// summary tracker so both runners deliver identical durable summaries.
832+
const deliverFollowupToolSummary = (payload: ReplyPayload) =>
833+
enqueueProgressDelivery(async () => {
834+
if (
835+
run.sourceReplyDeliveryMode === "message_tool_only" &&
836+
!shouldEmitToolResultProgress()
837+
) {
838+
return;
839+
}
840+
await sendFollowupPayloads(
841+
[payload],
842+
effectiveQueued,
843+
{
844+
provider,
845+
modelId: model,
846+
},
847+
{ kind: "tool", mirror: false, runId },
848+
);
849+
if (payload.isError === true) {
850+
markVisibleToolErrorProgress();
851+
}
852+
});
830853
try {
831854
if (isCliProvider(cliExecutionProvider, runtimeConfig)) {
832855
const cliSessionBinding = getCliSessionBinding(
@@ -845,27 +868,7 @@ export function createFollowupRunner(params: {
845868
detailMode: toolProgressDetail,
846869
shouldEmitToolResult: shouldEmitToolResultProgress,
847870
shouldEmitToolOutput: shouldEmitToolOutputProgress,
848-
deliver: (payload) =>
849-
enqueueProgressDelivery(async () => {
850-
if (
851-
run.sourceReplyDeliveryMode === "message_tool_only" &&
852-
!shouldEmitToolResultProgress()
853-
) {
854-
return;
855-
}
856-
await sendFollowupPayloads(
857-
[payload],
858-
effectiveQueued,
859-
{
860-
provider,
861-
modelId: model,
862-
},
863-
{ kind: "tool", mirror: false, runId },
864-
);
865-
if (payload.isError === true) {
866-
markVisibleToolErrorProgress();
867-
}
868-
}),
871+
deliver: deliverFollowupToolSummary,
869872
});
870873
const result = await runCliAgentWithLifecycle({
871874
runId,
@@ -1065,27 +1068,7 @@ export function createFollowupRunner(params: {
10651068
toolProgressDetail,
10661069
shouldEmitToolResult: shouldEmitToolResultProgress,
10671070
shouldEmitToolOutput: shouldEmitToolOutputProgress,
1068-
onToolResult: (payload) =>
1069-
enqueueProgressDelivery(async () => {
1070-
if (
1071-
run.sourceReplyDeliveryMode === "message_tool_only" &&
1072-
!shouldEmitToolResultProgress()
1073-
) {
1074-
return;
1075-
}
1076-
await sendFollowupPayloads(
1077-
[payload],
1078-
effectiveQueued,
1079-
{
1080-
provider,
1081-
modelId: model,
1082-
},
1083-
{ kind: "tool", mirror: false, runId },
1084-
);
1085-
if (payload.isError === true) {
1086-
markVisibleToolErrorProgress();
1087-
}
1088-
}),
1071+
onToolResult: deliverFollowupToolSummary,
10891072
onAgentEvent: (evt) =>
10901073
enqueueProgressDelivery(async () => {
10911074
await forwardFollowupProgressEvent({

0 commit comments

Comments
 (0)