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
13 changes: 13 additions & 0 deletions modules/mcp/mcp/git_hub/api/GitHubApiResponseDtos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,19 @@ internal sealed class PrListItemResponse {
/// <summary>作者</summary>
[JsonPropertyName("user")]
public UserRefResponse? User { get; init; }
/// <summary>源分支引用(head.ref — gh CLI BRANCH 列数据源;search API 精简响应可能缺省 null)</summary>
[JsonPropertyName("head")]
public PrHeadRefResponse? Head { get; init; }
/// <summary>目标分支引用(base.ref — 与源分支合成 head→base 列)</summary>
[JsonPropertyName("base")]
public PrHeadRefResponse? Base { get; init; }
}

/// <summary>PR 分支引用(head/base 共用 — pulls API {ref: "..."} 形态)</summary>
internal sealed class PrHeadRefResponse {
/// <summary>分支名</summary>
[JsonPropertyName("ref")]
public string Ref { get; init; } = "";
}

/// <summary>PR 列表 items 包装 — search API {items:[...]} 格式</summary>
Expand Down
20 changes: 17 additions & 3 deletions modules/mcp/mcp/git_hub/handlers/GitHubToolHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ private static string SummarizeRepo(string json) {
}

/// <summary>
/// 精简 PR 列表 JSON — 表格格式(number, state, title, author),兼容 pulls API 数组和 search API {items} 格式
/// 精简 PR 列表 JSON — 表格格式(number, state, title, author, branch head→base),兼容 pulls API 数组和 search API {items} 格式
/// </summary>
private static string SummarizePrList(string json) {
try {
Expand All @@ -561,14 +561,28 @@ private static string SummarizePrList(string json) {
}
if (prs is null) return json;
var sb = new StringBuilder(512);
sb.AppendLine("PR#\t状态\t标题\t作者");
foreach (var pr in prs) sb.AppendLine($"{pr.Number}\t{pr.State}\t{pr.Title}\t{pr.User?.Login ?? ""}");
sb.AppendLine("PR#\t状态\t标题\t作者\t分支");
foreach (var pr in prs) {
var branch = FormatPrBranch(pr);
sb.AppendLine($"{pr.Number}\t{pr.State}\t{pr.Title}\t{pr.User?.Login ?? ""}\t{branch}");
}
return sb.ToString();
} catch {
return json;
}
}

/// <summary>PR 分支列文案 — head→base(同分支省箭头;引用缺失回退空串——search API 精简响应无 head/base)。</summary>
private static string FormatPrBranch(PrListItemResponse pr) {
var head = pr.Head?.Ref ?? string.Empty;
var @base = pr.Base?.Ref ?? string.Empty;
if (head.Length == 0 && @base.Length == 0) {
return string.Empty;
}

return head == @base || @base.Length == 0 ? head : $"{head}→{@base}";
}

/// <summary>
/// 精简 Issue 列表 JSON — 表格格式(number, state, title, author)
/// </summary>
Expand Down
6 changes: 5 additions & 1 deletion native_tui/apps/NativeTui.Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ private static int RunBridgeInteractive(string exePath, string? model = null, bo
var version = typeof(Program).Assembly.GetName().Version is { } v ? v.ToString(3) : "0.0.0";
var info = SessionInfo.Create(version, model);
var banner = new BannerItem(info.Version, info.Model, info.Billing, info.WorkingDirectory);
var shell = new ReplShell(engine, banner, Console.Out, inline: !viewport, codeHighlighter: new TextMateCodeHighlighter(dark: true));
// M8-8 后续 — x 停止后台任务:bridge 模式经 IReplEngine 接口写子进程 stdin stop_task 请求
//(引擎侧分派 CancelFork/StopTeammate 后 cancelled 终态事件泵回推面板)
var shell = new ReplShell(engine, banner, Console.Out, inline: !viewport,
onStopTask: engine.StopBackgroundTask,
codeHighlighter: new TextMateCodeHighlighter(dark: true));
shell.Run();
shell.WriteExit();
}
Expand Down
17 changes: 7 additions & 10 deletions native_tui/apps/NativeTui.Host/EngineSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ private EngineSession(EngineSessionFactory.Result engine, EngineTurnClient clien
/// <summary>重发 session_info 事件 — 模型/供应商切换等运行时状态变化后刷新 banner(ReplBridgeDriver.ApplySessionInfo 差分上屏)。</summary>
public void RefreshSessionInfo() => _client.Enqueue(BuildSessionInfoEvent(_checker, _fastMode, _engine.Config));

/// <summary>停止后台任务(M8-8 后续 — Program 在 shell 构造处 onStopTask 委托接线,
/// 转发回合客户端 → 后台任务泵 CancelFork/StopTeammate 分派)。</summary>
/// <param name="kind">任务类别(fork|teammate)</param>
/// <param name="taskId">任务标识</param>
public void StopBackgroundTask(string kind, string taskId) => _client.StopBackgroundTask(kind, taskId);

/// <summary>TUI 静默日志 provider(internal — 测试断言非 Console:Console 直写 stderr 与 inline 渲染交错致光标错乱/横向线残影)。
/// 错误反馈走 EngineTurnClient Error 事件链(● 回合执行失败),不依赖引擎日志。</summary>
internal static ILoggerProvider LoggerProvider => SilentLoggerProvider.Instance;
Expand All @@ -74,8 +68,9 @@ public static async Task<EngineSession> CreateAsync(CancellationToken cancellati

// S8 confirm 闭环 — 命令确认回调走 TuiPermissionBridge TCS 桥(复用权限对话框模态,
// 两档映射 Allow/AlwaysAllow → true;shell 未绑定时 bridge 自动 Deny → false 安全降级)。
// prompt/readPassword 数据层(InputRequestCoordinator/PasswordMask)已就绪,
// ReplShell 输入模态入口属 S7 域 — 接线未决,期间保持 null(命令侧默认空输入降级)。
// prompt/readPassword 组装期 null(无 shell 可挂模态)— 交互模式 Program 在 shell 构造后
// BindInteractions 重绑为 ReplShell 命令对话框同步桥(prompt 明文 / readPassword 密文遮蔽,
// TASK065 F2b 已接;组装期 null = 命令侧默认空输入降级)。
commandCallbacks.BindInteractions(
confirm: message => MapConfirmResult(permissionBridge.Confirm(CommandConfirmToolName, message)),
prompt: null,
Expand Down Expand Up @@ -183,7 +178,8 @@ private static async Task RunTurnAsync(SessionController controller, string inpu
/// S8/TASK065 F2 交互面透传:confirm 组装期闭环走权限对话框 TCS 桥(selftest 等非交互宿主
/// 也有 confirm 能力);交互模式 Program 在 shell 构造后 BindInteractions 重绑 confirm/prompt
/// 为 ReplShell 命令对话框同步桥(语义更准;解锁 /commit /login /resume 等交互命令);
/// readPassword 双方均未接(密文输入框复杂度高 — 空输入降级,遗留项登记)。
/// readPassword 密文模态(TASK065 F2b — 回显 PasswordMask 等宽遮蔽、Enter 提交原文/Esc 取消 null,
/// 数据层原文仅存协调器,渲染层不落明文)。
/// TASK065 F6 — 命令回合标记 consumer 纯文本模式(OnDone 跳过 Markdig 投影,防 /help 类
/// 缩进对齐文本被解释为代码块/表格错位;对话回合 Markdown 不受影响)。
/// internal static — 回调透传链测试面(fake DI 驱动真实命令注册链)。
Expand Down Expand Up @@ -288,7 +284,8 @@ public void BindInteractions(Func<string, bool>? confirm, Func<string, string?>?
/// TASK065 F2,/login /resume 等。null — 命令侧默认空输入)。</summary>
public Func<string, string?>? Prompt => _prompt;

/// <summary>密码输入回调(命令 readPassword → TUI 遮蔽输入模态;null — 命令侧默认空串。双方均未接线,遗留项)。</summary>
/// <summary>密码输入回调(交互模式经 BindInteractions 重绑为 ReplShell 命令密文输入对话框同步桥 —
/// TASK065 F2b,回显 PasswordMask 遮蔽 + Enter 提交原文/Esc null。null — 命令侧默认空串降级)。</summary>
public Func<string, string?>? ReadPassword => _readPassword;

/// <summary>已绑定任一回调(internal — Program 接线测试断言面)。</summary>
Expand Down
12 changes: 7 additions & 5 deletions native_tui/apps/NativeTui.Host/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,10 @@ private static async Task<int> RunInteractiveAsync() {
themeName: themeName,
commandUsage: commandUsage,
workspaceRoot: Environment.CurrentDirectory,
// M8-8 后续 — x 停止后台任务委托:面板运行中条目 → 引擎泵 CancelFork/StopTeammate 分派
//(cancelled 终态 upsert 由泵推回面板即时反馈)
onStopTask: (kind, taskId) => session.StopBackgroundTask(kind, taskId),
// M8-8 后续 — x 停止后台任务委托:面板运行中条目 → 回合客户端停止入口(多态统一面 —
// 进程内 EngineTurnClient 泵分派 CancelFork/StopTeammate,bridge 形态走同接口的
// BridgeEngine 写 stop_task 请求;cancelled 终态 upsert 由泵推回面板即时反馈)
onStopTask: (kind, taskId) => session.Client.StopBackgroundTask(kind, taskId),
// 方案 B — 流式期 Markdown 块级增量投影(Markdig 卫星注入:稳定前缀 memoize +
// 增长尾纯文本,对齐基准 StreamingMarkdown;落定替换 MarkdownMessageItem 全量渲染)
streamingProjector: new StreamingMarkdownProjector());
Expand All @@ -317,11 +318,12 @@ private static async Task<int> RunInteractiveAsync() {
// TASK065 F2/S8 融合 — 交互面重绑:confirm/prompt 从组装期权限对话框初值升级为
// ReplShell 命令对话框同步桥(语义更准:命令确认非工具权限;/commit /login /resume 等
// 交互命令解锁 — 回合线程挂起等模态按键,对齐 TuiPermissionBridge 先例);
// readPassword 未接(密文输入框复杂度高 — /login 交互流程降级,遗留项登记)
// readPassword 密文模态(TASK065 F2b — 回显 PasswordMask 遮蔽 + Enter 提交原文/Esc null,
// /login 等密文输入命令解锁)
session.CommandCallbacks.BindInteractions(
confirm: shell.ConfirmCommand,
prompt: shell.PromptCommand,
readPassword: null);
readPassword: shell.ReadPasswordCommand);
shell.Run();
shell.WriteExit();
}
Expand Down
44 changes: 32 additions & 12 deletions native_tui/src/NativeTui.Core/bridge/BridgeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,35 @@ namespace NativeTui.Core.Bridge;

/// <summary>
/// bridge 引擎客户端 — TUI 父进程封装(ADR 0141):拉起引擎子进程(stdio 管道),
/// 请求写 stdin(Submit/Interrupt),事件由 BridgeEventPump 后台排空(DrainEvents)。
/// Dispose:发 quit → 关 stdin → 等引擎退出(无超时 — 关闭即释放)→ 等泵收尾。
/// 请求写 stdin(Submit/Interrupt/SetPermissionMode/StopBackgroundTask),事件由 BridgeEventPump
/// 后台排空(DrainEvents)。Dispose:发 quit → 关 stdin → 等引擎退出(无超时 — 关闭即释放)→ 等泵收尾。
/// 引擎 stderr 直通终端(不重定向):诊断可见且无管道缓冲填满死锁风险。
/// </summary>
public sealed class BridgeClient : IDisposable
{
private readonly Process _process;
private readonly TextWriter _stdin;
private readonly Process? _process;
private readonly BridgeEventPump _pump;

private BridgeClient(Process process, BridgeEventPump pump)
{
_process = process;
_stdin = process.StandardInput;
_pump = pump;
}

/// <summary>引擎进程是否仍存活。</summary>
public bool IsEngineAlive => !_process.HasExited;
/// <summary>测试构造 — 注入 stdin 写面与 stdout 读面(internal — BridgeClientTests 单测协议行;
/// 无子进程,Dispose 只收事件泵;对齐生产构造的泵启动形态)。</summary>
internal BridgeClient(TextWriter stdin, TextReader stdout)
{
_process = null;
_stdin = stdin;
_pump = new BridgeEventPump(stdout);
_pump.Start();
}

/// <summary>引擎进程是否仍存活(测试构造无子进程恒活 — 状态由事件泵 EOF 感知)。</summary>
public bool IsEngineAlive => _process is null || !_process.HasExited;

/// <summary>拉起引擎子进程并启动事件泵(exe 不存在 / 启动失败抛异常)。</summary>
public static BridgeClient Start(string exePath, string arguments = "")
Expand All @@ -39,15 +51,23 @@ public static BridgeClient Start(string exePath, string arguments = "")

/// <summary>提交用户输入(开始回合)。</summary>
public void Submit(string text) =>
BridgeProtocol.WriteRequest(_process.StandardInput, new BridgeRequest.Submit(text));
BridgeProtocol.WriteRequest(_stdin, new BridgeRequest.Submit(text));

/// <summary>打断当前回合。</summary>
public void Interrupt() =>
BridgeProtocol.WriteRequest(_process.StandardInput, new BridgeRequest.Interrupt());
BridgeProtocol.WriteRequest(_stdin, new BridgeRequest.Interrupt());

/// <summary>切换权限模式(mode 为协议值 plan/auto/ask/bypass/unattended;生效值以引擎回执事件为准)。</summary>
public void SetPermissionMode(string mode) =>
BridgeProtocol.WriteRequest(_process.StandardInput, new BridgeRequest.SetPermissionMode(mode));
BridgeProtocol.WriteRequest(_stdin, new BridgeRequest.SetPermissionMode(mode));

/// <summary>停止后台任务(M8-8 — kind: fork|teammate,写 stdin stop_task 请求;
/// 即发即返不等回执 — 引擎侧 ServeLoop 分派 CancelFork/StopTeammate 后回推 cancelled 终态
/// background_task 事件,面板即时反馈走事件链,对齐 Interrupt 的发送形态)。</summary>
/// <param name="kind">任务类别(fork|teammate)</param>
/// <param name="taskId">任务标识</param>
public void StopBackgroundTask(string kind, string taskId) =>
BridgeProtocol.WriteRequest(_stdin, new BridgeRequest.StopTask(kind, taskId));

/// <summary>排空事件队列(返回本次取出的全部事件)。</summary>
public IReadOnlyList<BridgeEvent> DrainEvents() => _pump.DrainEvents();
Expand All @@ -59,18 +79,18 @@ public void SetPermissionMode(string mode) =>
public void Dispose()
{
CloseEngineGracefully();
_process.WaitForExit();
_process?.WaitForExit();
_pump.Dispose();
_process.Dispose();
_process?.Dispose();
}

/// <summary>优雅关闭:发 quit + 关 stdin(引擎已退出/管道断开时跳过 — 尽力而为)。</summary>
private void CloseEngineGracefully()
{
try
{
BridgeProtocol.WriteRequest(_process.StandardInput, new BridgeRequest.Quit());
_process.StandardInput.Close();
BridgeProtocol.WriteRequest(_stdin, new BridgeRequest.Quit());
_stdin.Close();
}
catch (IOException)
{
Expand Down
4 changes: 4 additions & 0 deletions native_tui/src/NativeTui.Core/bridge/BridgeEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public sealed class BridgeEngine : IReplEngine, IDisposable
/// <summary>请求权限模式切换 — 写子进程 stdin(回执事件为准)。</summary>
public void SetPermissionMode(string mode) => _client.SetPermissionMode(mode);

/// <summary>停止后台任务(M8-8 — 写子进程 stdin stop_task 请求;cancelled 终态 background_task
/// 事件由泵回推面板,即发即返不等回执)。</summary>
public void StopBackgroundTask(string kind, string taskId) => _client.StopBackgroundTask(kind, taskId);

/// <summary>排空事件泵待应用事件。</summary>
public IReadOnlyList<BridgeEvent> DrainEvents() => _client.DrainEvents();

Expand Down
7 changes: 7 additions & 0 deletions native_tui/src/NativeTui.Core/bridge/IReplEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ public interface IReplEngine
/// <summary>排空诊断旁路(shell 写 stderr;无诊断实现返回空 — 默认空)。</summary>
IReadOnlyList<string> DrainDiagnostics() => Array.Empty<string>();

/// <summary>停止后台任务(M8-8 — kind: fork|teammate;默认空实现 — 无后台任务能力的实现
/// (模拟引擎/测试桩)零成本,对齐 DrainDiagnostics 默认成员先例;进程内 EngineTurnClient
/// 泵分派 / 子进程 BridgeEngine 写 stop_task 请求为显式实现)。</summary>
/// <param name="kind">任务类别(fork|teammate)</param>
/// <param name="taskId">任务标识</param>
void StopBackgroundTask(string kind, string taskId) { }

/// <summary>引擎存活(进程内恒 true;子进程退出后 false — shell 空闲时追加退出消息并结束)。</summary>
bool IsAlive { get; }
}
Loading