Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Contracts;
4
5namespace CascadeIDE.Features.Chat.Application;
6
7/// <summary>
8/// Сценарий отправки сообщения Intercom: слэш → сборка outbound → spine → лента → провайдер (ADR 0119).
9/// Трассировка только на границах фаз.
10/// </summary>
11[ApplicationOrchestrator("intercom-outbound-send")]
12[UiThreadMarshal("host callbacks marshal UI where needed")]
13public static class IntercomOutboundSendOrchestrator
14{
15 public static Task RunAsync(IntercomOutboundSendHost host, CancellationToken cancellationToken = default) =>
16 IntercomSendTrace.RunAsync(host.GetWorkspaceRoot(), IntercomSendPhases.SendChat.Root, rootPhase =>
17 runCoreAsync(host, rootPhase, cancellationToken));
18
19 private static async Task runCoreAsync(
20 IntercomOutboundSendHost host,
21 IntercomSendPhase rootPhase,
22 CancellationToken cancellationToken)
23 {
24 var rawInput = host.GetTrimmedInput();
25 if (string.IsNullOrEmpty(rawInput))
26 return;
27
28 var workspaceRoot = host.GetWorkspaceRoot();
29 rootPhase.Detail($"input_len={rawInput.Length} pending_attach={host.GetPendingAttachCount()}");
30
31 var slashHandled = await IntercomSendTrace.RunAsync(
32 workspaceRoot,
33 IntercomSendPhases.SendChat.Slash,
34 _ => host.TryHandleSlashLineAsync(rawInput)).ConfigureAwait(false);
35 if (slashHandled)
36 return;
37
38 (bool Ok, IntercomAttachmentMessageBuilder.Outbound Outbound, string Error) build;
39 await host.BeginPrepareOutboundAsync().ConfigureAwait(false);
40 try
41 {
42 build = await IntercomSendTrace.RunAsync(
43 workspaceRoot,
44 IntercomSendPhases.SendChat.BuildOutbound,
45 async buildPhase =>
46 {
47 var result = await host.TryBuildOutboundAsync(rawInput, cancellationToken).ConfigureAwait(false);
48 if (!result.Ok)
49 {
50 buildPhase.Detail("failed: " + result.Error);
51 return result;
52 }
53
54 buildPhase.Detail(
55 $"ok attachments={result.Outbound.Attachments.Count} content_len={result.Outbound.Content.Length}");
56 return result;
57 }).ConfigureAwait(false);
58 }
59 finally
60 {
61 await host.EndPrepareOutboundAsync().ConfigureAwait(false);
62 }
63
64 if (!build.Ok)
65 {
66 await host.SetClarificationStatusAsync(build.Error).ConfigureAwait(false);
67 return;
68 }
69
70 var prepared = IntercomSendTrace.Run(
71 workspaceRoot,
72 IntercomSendPhases.SendChat.PrepareMessage,
73 preparePhase =>
74 {
75 var display = host.ApplyProductSpine(build.Outbound.Content);
76 if (string.IsNullOrEmpty(display))
77 return (Display: (string?)null, Agent: (string?)null);
78
79 var agent = host.FormatAgentInput(display, build.Outbound);
80 preparePhase.Detail($"display_len={display.Length} agent_len={agent.Length}");
81 return (Display: display, Agent: agent);
82 });
83
84 if (prepared.Display is null)
85 {
86 await host.SetClarificationStatusAsync("Сообщение пустое после подготовки.").ConfigureAwait(false);
87 return;
88 }
89
90 var displayInput = prepared.Display;
91 var agentInput = prepared.Agent!;
92 var mcpOnly = host.GetChatMcpOnly();
93 var deliveryMode = host.ConsumeDeliveryMode();
94 host.CancelActiveTurnIfSteer(deliveryMode);
95 var deferProvider = host.ShouldDeferProviderDispatch(deliveryMode);
96 var startProviderLoading = !mcpOnly && !deferProvider;
97 var dispatchedProvider = false;
98
99 try
100 {
101 await IntercomSendTrace.RunAsync(
102 workspaceRoot,
103 IntercomSendPhases.SendChat.CommitFeed,
104 _ => host.CommitUserMessageAsync(displayInput, build.Outbound, startProviderLoading, deliveryMode))
105 .ConfigureAwait(false);
106
107 if (mcpOnly)
108 return;
109
110 if (deferProvider)
111 {
112 await host.EnqueueFollowUpAgentInputAsync(agentInput).ConfigureAwait(false);
113 return;
114 }
115
116 dispatchedProvider = true;
117 await IntercomSendTrace.RunAsync(
118 workspaceRoot,
119 IntercomSendPhases.SendChat.DispatchProvider,
120 async dispatchPhase =>
121 {
122 var provider = host.GetActiveAiProvider();
123 dispatchPhase.Detail($"provider={provider} agent_input_len={agentInput.Length} delivery={deliveryMode}");
124
125 if (string.Equals(provider, "CursorACP", StringComparison.Ordinal))
126 await host.SendCursorAcpAsync(agentInput).ConfigureAwait(false);
127 else
128 await host.SendStreamingAsync(agentInput, displayInput).ConfigureAwait(false);
129 }).ConfigureAwait(false);
130 }
131 catch (Exception ex)
132 {
133 rootPhase.Detail("error: " + ex);
134 throw;
135 }
136 finally
137 {
138 if (dispatchedProvider)
139 await host.EndProviderTurnAsync().ConfigureAwait(false);
140 }
141 }
142}
143
View only · write via MCP/CIDE