Run CDB directly

This commit is contained in:
vczh
2026-08-02 00:54:37 -07:00
parent 92f80aa5e3
commit 5cb97f351e
21 changed files with 222 additions and 164 deletions
@@ -10,7 +10,7 @@ The copied `.github` folder contains several kinds of files:
- `copilot-instructions.md`: the main instruction file. It tells the agent to read `Project.md`, use the knowledge base, and prefer provided scripts.
- `Guidelines`: build, run, debug, source-file, coding, and GacUI resource instructions.
- `prompts`: job prompts such as `ask.prompt.md`, `investigate.prompt.md`, `refine.prompt.md`, and `kb.prompt.md`.
- `Scripts`: Windows PowerShell wrappers for building, executing, debugging, and archiving task logs.
- `Scripts`: Windows PowerShell wrappers for building, executing, and archiving task logs.
- `Ubuntu`: Linux build wrapper and helper commands.
- `KnowledgeBase`: copied API, design, manual, and learning documents that the agent can read without network access.
- `Learning`: project-local lessons that refine future agent behavior.
@@ -1,15 +1,16 @@
# Installing Required Tools on Windows
The Windows agent scripts build with MSBuild through Visual Studio's developer environment and debug with CDB. Install Visual Studio with the C++ desktop workload, a Windows SDK, and the Windows debugging tools that provide `cdb.exe`. If CDB is not available, install the Windows Driver Kit or the Windows debugging tools component from the Visual Studio Installer.
The Windows agent scripts build with MSBuild through Visual Studio's developer environment, and the agent debugs directly with CDB. Install Visual Studio with the C++ desktop workload, a Windows SDK, and the Windows debugging tools that provide `cdb.exe`. If CDB is not available, install the Windows Driver Kit or the Windows debugging tools component from the Visual Studio Installer.
Define these environment variables before asking the agent to build or debug:
- `VLPP_VSDEVCMD_PATH`: absolute path to `VsDevCmd.bat`, for example `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat`.
- `CDBPATH`: absolute path to `cdb.exe`, for example `C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe`.
After copying the Release `.github` folder, the agent should use these scripts instead of calling tools directly:
After copying the Release `.github` folder, the agent should use these scripts instead of calling build or execution tools directly:
- `.github\Scripts\copilotBuild.ps1`: builds the solution found from `Project.md` context and writes `Build.log`.
- `.github\Scripts\copilotExecute.ps1`: runs unit-test or CLI projects and writes `Execute.log` for unit tests.
- `.github\Scripts\copilotDebug_Start.ps1` and `copilotDebug_RunCommand.ps1`: start CDB and send debugger commands.
Run CDB directly in an interactive terminal when debugging.
If the application uses the tools shipped by Release, build them from the Release repository:
- Open `Tools\Executables\Executables.sln` in Visual Studio.
@@ -17,5 +18,5 @@ If the application uses the tools shipped by Release, build them from the Releas
- Run `Tools\CopyExecutables.ps1`.
- Confirm `CodePack.exe`, `CppMerge.exe`, `GacGen.exe`, and `GlrParserGen.exe` exist in `Release\Tools`.
For debugger readability, copy `Import\vlpp.natvis` from Release to Visual Studio's visualizers folder. The CDB startup script also loads the natvis file for debugger commands such as `dx`.
For debugger readability, copy `Import\vlpp.natvis` from Release to Visual Studio's visualizers folder. Load the natvis file in CDB for debugger commands such as `dx`.
@@ -1,6 +1,6 @@
# Investigate and Refine Jobs
`AGENTS.md` and `CLAUDE.md` route short request keywords to prompt files in `.github/prompts`. These jobs make agent work repeatable because the agent writes durable task documents, uses the copied knowledge base, and follows the repository's build and debug scripts.
`AGENTS.md` and `CLAUDE.md` route short request keywords to prompt files in `.github/prompts`. These jobs make agent work repeatable because the agent writes durable task documents, uses the copied knowledge base, and follows the repository's build, run, and debugging instructions.
## investigate
@@ -8,7 +8,7 @@ The service is independent from the remote protocol. A normal Windows applicatio
`INativeAutomationService` has one service-level availability flag and three feature groups:
- `Available`: returns false when no automation service exists for the current controller.
- `Stop`: turns off all features. Windows implementations also stop the HTTP listener.
- `Stop`: turns off all features. Endpoint lifetime is owned separately.
- `CanDumpControlTree` and `DumpControlTree`: expose visible GacUI windows, popups, controls and compositions.
- `CanDumpDomTree` and `DumpDomTree`: expose the remote protocol renderer DOM.
- `CanRunIOCommands` and `RunIOCommand`: send a textual IO command to the main window or to a selected native window.
@@ -22,7 +22,7 @@ Feature availability is checked separately. A real service returns true from `Av
## Windows HTTP Layer
`StartWindowsHttpAutomationService` creates a localhost HTTP wrapper around the current `INativeAutomationService`. It is declared in `PlatformProviders/Windows/WinNativeWindow.h` and implemented by the Windows platform provider. If `GetCurrentController()->AutomationService()->Available()` is false, the function returns without starting a listener.
`StartWindowsHttpAutomationService` creates a localhost HTTP wrapper around the current `INativeAutomationService`. The test-support implementation lives in `Source/RemotingHelpers/AutomationService/Windows`, outside the ordinary `GacUI.Windows` library pair. Test applications consume it through the shared `Source_RemotingHelpers` project.
The function takes `applicationName` as a URL path fragment and `port` as the localhost port. Given `applicationName == L"Automation/MyApp"` and `port == 8888`, the listener prefix is `http://localhost:8888/Automation/MyApp/`. The service offers exactly these HTTP URLs:
- `GET http://localhost:8888/Automation/MyApp/Controls`: calls `DumpControlTree` on the UI thread when `CanDumpControlTree` is true.
@@ -34,40 +34,31 @@ The window id is a path segment after `IO`, not a query parameter. All other met
## Starting The Service
Call `StartWindowsHttpAutomationService` from `GuiMain`, after the setup function has installed the current native controller and before entering the application event loop. Every code path that calls `StartWindowsHttpAutomationService` must later call `StopWindowsHttpAutomationService` before the native controller or substituted automation service is torn down. Skipping the stop leaks the process-wide HTTP service. Use a local guard or equivalent try/catch so the stop runs after the start on normal returns and exceptions.
Each application owns the automation service and endpoint directly. After the setup function has installed the current native controller, construct the concrete service matching that controller, substitute it, start an endpoint, and enter the application event loop. On exit, reverse the dependency order: stop the endpoint, stop the service, and finally unsubstitute it.
A normal Windows application can start the service before `GetApplication()->Run`:
```c++
#include "../../../Source/PlatformProviders/Windows/WinNativeWindow.h"
#include "../../../Source/RemotingHelpers/AutomationService/Windows/WindowsAutomationService.Windows.h"
using namespace vl;
using namespace vl::presentation;
using namespace vl::presentation::controls;
class WindowsHttpAutomationServiceScope
{
public:
WindowsHttpAutomationServiceScope(const WString& applicationName, vint port)
{
windows::StartWindowsHttpAutomationService(applicationName, port);
}
~WindowsHttpAutomationServiceScope()
{
windows::StopWindowsHttpAutomationService();
}
};
void GuiMain()
{
demo::MainWindow window;
window.ForceCalculateSizeImmediately();
window.MoveToScreenCenter();
WindowsHttpAutomationServiceScope httpAutomationService(
windows::WindowsAutomationServiceHosted automationService;
GetNativeServiceSubstitution()->Substitute(&automationService, false);
windows::StartWindowsHttpAutomationService(
WString::Unmanaged(L"Automation/MyApp"),
8888);
GetApplication()->Run(&window);
windows::StopWindowsHttpAutomationService();
automationService.Stop();
GetNativeServiceSubstitution()->Unsubstitute(&automationService);
}
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
@@ -76,46 +67,42 @@ int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
}
```
Repeated calls do not create multiple listeners. The Windows implementation keeps one process-wide HTTP service until `StopWindowsHttpAutomationService` stops it.
Only one substituted automation service and one endpoint may be active in a process. Duplicate starts fail immediately. `StartMiniHttpAutomationService(socketServer, applicationName)` follows the same lifecycle and URL contract, but requires the application to pass the exact `IAsyncSocketServer` that should host its automation prefix.
## Setup Function Cases
The setup function decides which controller and service are active while `GuiMain` runs. Use the following cases when deciding whether user code must substitute another automation service:
- `SetupWindowsGDIRenderer`: installs `WindowsAutomationService`. Use `StartWindowsHttpAutomationService` directly for multi-window control tree and IO. The DOM route is normally unavailable.
- `SetupWindowsDirect2DRenderer`: same automation behavior as `SetupWindowsGDIRenderer`, with the Direct2D renderer.
- `SetupHostedWindowsGDIRenderer`: installs `WindowsAutomationServiceHosted`. Use the HTTP service directly. IO should use `/IO` without a window id because hosted sub windows and popups are represented under the main window.
- `SetupHostedWindowsDirect2DRenderer`: same automation behavior as `SetupHostedWindowsGDIRenderer`, with the Direct2D renderer.
- `SetupRawWindowsGDIRenderer`: installs `WindowsAutomationService` for the raw native window controller. If the raw process is a remote protocol renderer and owns a `GuiRemoteRendererSingle`, substitute `WindowsAutomationServiceRenderer` to expose renderer DOM and renderer-side IO.
- `SetupRawWindowsDirect2DRenderer`: same automation behavior as `SetupRawWindowsGDIRenderer`, with the Direct2D renderer.
The setup function decides which controller is active while `GuiMain` runs; it no longer installs test automation implicitly. Select the matching helper service explicitly:
- `SetupWindowsGDIRenderer` and `SetupWindowsDirect2DRenderer`: construct `WindowsAutomationService` for multi-window control tree and IO. The DOM route is normally unavailable.
- `SetupHostedWindowsGDIRenderer` and `SetupHostedWindowsDirect2DRenderer`: construct `WindowsAutomationServiceHosted`. IO should use `/IO` without a window id because hosted sub windows and popups are represented under the main window.
- `SetupRawWindowsGDIRenderer` and `SetupRawWindowsDirect2DRenderer`: construct `WindowsAutomationService` for an ordinary raw native controller. A remote protocol renderer that owns a `GuiRemoteRendererSingle` constructs `WindowsAutomationServiceRenderer` with the renderer pointer to expose renderer DOM and renderer-side IO.
- `SetupRemoteNativeController`: the remote controller itself does not return an automation service. Substitute `RemoteProtocolAutomationService` around `GetApplication()->Run` when the remote core should expose the hosted control tree and core-side IO.
- `SetupGacGenNativeController`: this setup is for generation-time resource processing, not an interactive UI session. It does not provide an automation service for HTTP control.
- `SetupGtkRenderer`: do not call the Windows HTTP helper. A Gtk port must provide its own endpoint layer and automation service implementation if it needs coding-agent automation.
- `SetupWGacRenderer`: do not call the Windows HTTP helper. A Wayland port must provide its own endpoint layer and automation service implementation if it needs coding-agent automation.
- `SetupWGacHostedRenderer`: same requirement as `SetupWGacRenderer`, but the service should follow hosted-mode window-id behavior if it exposes hosted windows.
- `SetupWGacRenderer`: construct the Wayland platform service such as `WGacAutomationServiceRenderer` and expose it through MiniHTTP.
- `SetupWGacHostedRenderer`: use the corresponding hosted Wayland service and hosted-mode window-id behavior.
- A macOS remote renderer constructs `CocoaAutomationServiceRenderer` and exposes it through MiniHTTP.
## Substituting a Service
Use `GetNativeServiceSubstitution()->Substitute(service, false)` before the automation service is first requested. The substitution layer rejects a late substitution after a service has already been used. Keep the substituted object alive until it is unsubstituted.
Use `GetNativeServiceSubstitution()->Substitute(service, false)` before the automation service is first requested. The substitution layer rejects a late substitution after a service has already been used. Keep the concrete service alive until the endpoint has stopped, `service.Stop()` has completed, and the service is unsubstituted.
A remote protocol core can expose the core-side automation surface like this. The sample uses the same `WindowsHttpAutomationServiceScope` guard from the normal Windows application example.
A remote protocol core owns its neutral service and endpoint directly:
```c++
void GuiMain()
{
RemoteProtocolAutomationService automationService;
GetNativeServiceSubstitution()->Substitute(&automationService, false);
{
WindowsHttpAutomationServiceScope httpAutomationService(
WString::Unmanaged(L"Automation/RemoteCore"),
8888);
GetApplication()->Run(mainWindow);
}
windows::StartWindowsHttpAutomationService(
WString::Unmanaged(L"Automation/RemoteCore"),
8888);
GetApplication()->Run(mainWindow);
windows::StopWindowsHttpAutomationService();
automationService.Stop();
GetNativeServiceSubstitution()->Unsubstitute(&automationService);
}
```
A remote protocol renderer that owns a `GuiRemoteRendererSingle` can substitute `WindowsAutomationServiceRenderer` in the same scope before starting the HTTP service. This is the case where `GET /Dom` becomes meaningful.
A Windows remote protocol renderer constructs `WindowsAutomationServiceRenderer` with its `GuiRemoteRendererSingle`, substitutes it, and owns either the Windows HTTP or MiniHTTP endpoint directly. Linux uses `WGacAutomationServiceRenderer`; macOS uses `CocoaAutomationServiceRenderer`; both use MiniHTTP. These renderer cases are where `GET /Dom` becomes meaningful.
When a remote renderer is retained after a fatal remote-protocol error, renderer automation keeps `GET /Dom` available. The DOM response is still an HTTP success containing the frozen renderer DOM, and it adds a lowercase top-level `fatalError` string with the original error. Renderer IO switches to `ExitOnly`: ordinary IO returns exactly `!Application stopped responding.`, while exact `!Exit` is still accepted so automation can close the retained renderer.
@@ -15,7 +15,7 @@ using namespace vl::presentation::remote_renderer;
GuiRemoteRendererSingle* remoteRenderer = nullptr;
GuiRemoteProtocolAsyncJsonChannelRenderer* asyncChannel = nullptr;
class GuiMainInvoker : public IGuiRemoteProtocolAsyncRendererInvoker
class GuiMainInvoker : public Object, public virtual IGuiRemoteProtocolAsyncRendererInvoker
{
public:
void InvokeInMainThread(const Func<void()>& proc) override
@@ -29,9 +29,10 @@ void GuiMain()
auto mainWindow = GetCurrentController()->WindowService()->CreateNativeWindow(INativeWindow::Normal);
mainWindow->SetTitle(L"Connecting ...");
GuiMainInvoker invoker;
auto invoker = Ptr(new GuiMainInvoker);
remoteRenderer->RegisterMainWindow(mainWindow);
asyncChannel->SetInvokeInMainThread(&invoker);
asyncChannel->SetInvokeInMainThread(invoker);
asyncChannel->ProcessPendingMessages();
GetCurrentController()->WindowService()->Run(mainWindow);
@@ -66,5 +67,4 @@ GuiRemoteProtocolAsyncJsonChannelRenderer queues messages from the channel and r
The portable **/MiniHttp** path creates a default loopback TCP client with **vl::inter_process::async_tcp_socket::CreateDefaultAsyncSocketClient(port)**, wraps it in **vl::inter_process::async_tcp_socket::SocketHttpClient(socketClient, L"localhost", urlPrefix)**, and passes that object to **GuiRemoteProtocolChannelClient**. The renderer application shown by the linked test remains Win32 even though the VlppOS transport itself is available on Windows, Linux and macOS.
Override GuiRemoteProtocolChannelClient::OnReadError, OnLocalError, or OnDisconnected when the renderer should show a fatal error dialog or call GuiRemoteRendererSingle::ForceExitByFatelError. See [RemotingTest_Rendering_Win32](https://github.com/vczh-libraries/GacUI/tree/master/Test/GacUISrc/RemotingTest_Rendering_Win32) for the complete named-pipe (**/Pipe**), Windows HTTP.sys/WinHTTP (**/Http**) and portable Mini HTTP (**/MiniHttp**) implementations.
Override `GuiRemoteProtocolChannelClient::OnReadError` when a Core-authored `!Error` should show fatal UI. Treat `OnLocalError(..., true)` as an independently complete, prompt-free disconnected transition and do not wait for `OnDisconnected`; use `OnDisconnected` as the idempotent fallback when it is delivered. See [RemotingTest_Rendering_Win32](https://github.com/vczh-libraries/GacUI/tree/master/Test/GacUISrc/RemotingTest_Rendering_Win32) for the complete named-pipe (**/Pipe**), Windows HTTP.sys/WinHTTP (**/Http**) and portable Mini HTTP (**/MiniHttp**) implementations.
@@ -172,6 +172,8 @@ The bridge uses **NetworkPackage** as the raw text envelope. Its string shape is
A network channel client connects by sending one handshake package with an empty client id, an empty channel name and all supported channel names joined by **!**. The server validates the names, assigns a positive client id, records channel membership and sends the id back in an empty-channel response. After that, normal channel packages are delivered only when sender and receiver membership matches the channel name.
**BroadcastError** is a terminal admission boundary. The first broadcast error is retained, later broadcasts are ignored, and new network or local admissions are rejected. If an application **OnClientConnected** callback was already running, the server keeps the underlying transport alive until an accepted client receives that retained error and disconnects. A committed client is not included in the broadcast snapshot until its client-id response or local **OnConnected** callback completes, preserving connected-before-fatal ordering. Concurrent **Stop** calls share this barrier and cannot stop the transport early. Callback-reentrant terminal calls never wait on themselves; when broadcast owns the terminal boundary, physical stop is deferred until protected raw protocol, admission, fatal-delivery and disconnection callbacks unwind. If the last barrier is a raw protocol callback, completion moves to another thread so the underlying transport can drain that callback. A non-reentrant **Stop** reports a recorded shutdown exception once; a most-derived server destructor must call **Stop** before destroying callback-visible fields and catch any exception.
A channel server over a Windows named pipe can be declared like this:
```C++
#include <vlppos.h>
@@ -265,7 +267,7 @@ The raw HTTP protocol uses these routes under the configured base URL:
- **POST /VlppInterProcess/Request/GUID** is the client-maintained long-poll request for server-to-client messages.
- **POST /VlppInterProcess/Response/GUID** sends client-to-server messages and may also receive one queued server-to-client message.
**vl::inter_process::windows_http::HttpClient::WaitForServer** sends the connect request, validates the returned URLs, records them and reports connection. **BeginReadingLoopUnsafe** starts the long-poll request loop. **SendString** posts to the response URL. Connect and response failures retry a limited number of times; request failures retry while the client is still running.
**vl::inter_process::windows_http::HttpClient::WaitForServer** sends the connect request, validates the returned URLs, records them and reports connection. **BeginReadingLoopUnsafe** starts the long-poll request loop. **SendString** posts to the response URL. Each recoverable failed Connect, Request or Response exchange calls **INetworkProtocolCallback::OnLocalError** with **fatal == false** before retrying; Connect and Response retry a limited number of times, while Request retries while the client is still running. Bounded retry exhaustion remains raw-fatal. Returning **true** from **OnLocalError** promotes a recoverable failure and makes the raw client stop after the callback returns. **NetworkProtocolChannelClient** does this for every local error after its channel reaches **Connected**, forwards **fatal == true** to its **IChannelClient** user and disconnects the channel; pre-handshake Connect retries remain under raw-client policy.
**vl::inter_process::windows_http::HttpServer** creates a **vl::inter_process::windows_http::HttpServerConnection** for each connect request. Server-to-client messages are returned through a pending long-poll request when possible, or queued until the next request. Client-to-server request bodies are dispatched as inbound strings. When the server stops, pending long-poll requests are cancelled and connection callbacks receive disconnection.