deadbyte-mcp 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +92 -38
- package/README.txt +5 -4
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +10 -8
- package/scripts/build-desktop-r40.ps1 +106 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/init-desktop-policy-r40.ps1 +79 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +22 -0
- package/src/autonomous-runtime.mjs +144 -24
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +72 -44
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +74 -19
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +208 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +85 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +14 -14
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +19 -61
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#define WIN32_LEAN_AND_MEAN
|
|
2
|
+
#include <windows.h>
|
|
3
|
+
#include <io.h>
|
|
4
|
+
#include <fcntl.h>
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
#include <sstream>
|
|
10
|
+
#include <iomanip>
|
|
11
|
+
|
|
12
|
+
struct MonitorRow {
|
|
13
|
+
RECT rect{};
|
|
14
|
+
bool primary=false;
|
|
15
|
+
std::string device;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
static std::string Utf8(const wchar_t* value) {
|
|
19
|
+
if (!value) return {};
|
|
20
|
+
int needed = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, nullptr, 0, nullptr, nullptr);
|
|
21
|
+
if (needed <= 0) return {};
|
|
22
|
+
std::string out(static_cast<size_t>(needed), '\0');
|
|
23
|
+
if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, out.data(), needed, nullptr, nullptr) <= 0) return {};
|
|
24
|
+
if (!out.empty() && out.back() == '\0') out.pop_back();
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static std::string JsonEscape(const std::string& input) {
|
|
29
|
+
std::ostringstream out;
|
|
30
|
+
for (unsigned char ch : input) {
|
|
31
|
+
switch (ch) {
|
|
32
|
+
case '"': out << "\\\""; break;
|
|
33
|
+
case '\\': out << "\\\\"; break;
|
|
34
|
+
case '\b': out << "\\b"; break;
|
|
35
|
+
case '\f': out << "\\f"; break;
|
|
36
|
+
case '\n': out << "\\n"; break;
|
|
37
|
+
case '\r': out << "\\r"; break;
|
|
38
|
+
case '\t': out << "\\t"; break;
|
|
39
|
+
default:
|
|
40
|
+
if (ch < 0x20) {
|
|
41
|
+
out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<unsigned int>(ch) << std::dec;
|
|
42
|
+
} else out << static_cast<char>(ch);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out.str();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static BOOL CALLBACK MonitorProc(HMONITOR monitor, HDC, LPRECT, LPARAM param) {
|
|
49
|
+
auto* rows = reinterpret_cast<std::vector<MonitorRow>*>(param);
|
|
50
|
+
MONITORINFOEXW info{};
|
|
51
|
+
info.cbSize = sizeof(info);
|
|
52
|
+
if (!GetMonitorInfoW(monitor, &info)) return TRUE;
|
|
53
|
+
MonitorRow row{};
|
|
54
|
+
row.rect = info.rcMonitor;
|
|
55
|
+
row.primary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
|
|
56
|
+
row.device = Utf8(info.szDevice);
|
|
57
|
+
rows->push_back(row);
|
|
58
|
+
return TRUE;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static bool InputDesktopName(std::string& name) {
|
|
62
|
+
HDESK desktop = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
|
|
63
|
+
if (!desktop) return false;
|
|
64
|
+
wchar_t buffer[512]{};
|
|
65
|
+
DWORD needed = 0;
|
|
66
|
+
BOOL ok = GetUserObjectInformationW(desktop, UOI_NAME, buffer, sizeof(buffer), &needed);
|
|
67
|
+
CloseDesktop(desktop);
|
|
68
|
+
if (!ok) return false;
|
|
69
|
+
name = Utf8(buffer);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static std::string HwndHex(HWND hwnd) {
|
|
74
|
+
std::ostringstream out;
|
|
75
|
+
out << "0x" << std::hex << std::nouppercase << reinterpret_cast<uintptr_t>(hwnd);
|
|
76
|
+
return out.str();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
int wmain() {
|
|
80
|
+
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
81
|
+
|
|
82
|
+
std::string inputDesktop;
|
|
83
|
+
if (!InputDesktopName(inputDesktop)) {
|
|
84
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: input desktop unavailable; refusing capture\n");
|
|
85
|
+
return 10;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const int left = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
89
|
+
const int top = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
90
|
+
const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
91
|
+
const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
92
|
+
if (width <= 0 || height <= 0 || width > 32768 || height > 32768) {
|
|
93
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: virtual desktop bounds invalid\n");
|
|
94
|
+
return 11;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const uint64_t stride = static_cast<uint64_t>(width) * 4u;
|
|
98
|
+
const uint64_t bytes = stride * static_cast<uint64_t>(height);
|
|
99
|
+
if (bytes == 0 || bytes > 256ull * 1024ull * 1024ull) {
|
|
100
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: frame exceeds 256 MiB bound\n");
|
|
101
|
+
return 12;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
HDC screen = GetDC(nullptr);
|
|
105
|
+
if (!screen) {
|
|
106
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: GetDC failed %lu\n", GetLastError());
|
|
107
|
+
return 13;
|
|
108
|
+
}
|
|
109
|
+
HDC memory = CreateCompatibleDC(screen);
|
|
110
|
+
if (!memory) {
|
|
111
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: CreateCompatibleDC failed %lu\n", GetLastError());
|
|
112
|
+
ReleaseDC(nullptr, screen);
|
|
113
|
+
return 14;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
BITMAPINFO bmi{};
|
|
117
|
+
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
118
|
+
bmi.bmiHeader.biWidth = width;
|
|
119
|
+
bmi.bmiHeader.biHeight = -height;
|
|
120
|
+
bmi.bmiHeader.biPlanes = 1;
|
|
121
|
+
bmi.bmiHeader.biBitCount = 32;
|
|
122
|
+
bmi.bmiHeader.biCompression = BI_RGB;
|
|
123
|
+
|
|
124
|
+
void* bits = nullptr;
|
|
125
|
+
HBITMAP bitmap = CreateDIBSection(screen, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
|
126
|
+
if (!bitmap || !bits) {
|
|
127
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: CreateDIBSection failed %lu\n", GetLastError());
|
|
128
|
+
if (bitmap) DeleteObject(bitmap);
|
|
129
|
+
DeleteDC(memory);
|
|
130
|
+
ReleaseDC(nullptr, screen);
|
|
131
|
+
return 15;
|
|
132
|
+
}
|
|
133
|
+
HGDIOBJ old = SelectObject(memory, bitmap);
|
|
134
|
+
if (!old || old == HGDI_ERROR) {
|
|
135
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: SelectObject failed %lu\n", GetLastError());
|
|
136
|
+
DeleteObject(bitmap);
|
|
137
|
+
DeleteDC(memory);
|
|
138
|
+
ReleaseDC(nullptr, screen);
|
|
139
|
+
return 16;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!BitBlt(memory, 0, 0, width, height, screen, left, top, SRCCOPY | CAPTUREBLT)) {
|
|
143
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: BitBlt failed %lu\n", GetLastError());
|
|
144
|
+
SelectObject(memory, old);
|
|
145
|
+
DeleteObject(bitmap);
|
|
146
|
+
DeleteDC(memory);
|
|
147
|
+
ReleaseDC(nullptr, screen);
|
|
148
|
+
return 17;
|
|
149
|
+
}
|
|
150
|
+
GdiFlush();
|
|
151
|
+
|
|
152
|
+
std::vector<MonitorRow> monitors;
|
|
153
|
+
if (!EnumDisplayMonitors(nullptr, nullptr, MonitorProc, reinterpret_cast<LPARAM>(&monitors)) || monitors.empty()) {
|
|
154
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: monitor enumeration failed %lu\n", GetLastError());
|
|
155
|
+
SelectObject(memory, old);
|
|
156
|
+
DeleteObject(bitmap);
|
|
157
|
+
DeleteDC(memory);
|
|
158
|
+
ReleaseDC(nullptr, screen);
|
|
159
|
+
return 18;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
HWND foreground = GetForegroundWindow();
|
|
163
|
+
DWORD foregroundPid = 0;
|
|
164
|
+
std::string foregroundTitle;
|
|
165
|
+
std::string foregroundImage;
|
|
166
|
+
if (foreground) {
|
|
167
|
+
GetWindowThreadProcessId(foreground, &foregroundPid);
|
|
168
|
+
const int titleLength = GetWindowTextLengthW(foreground);
|
|
169
|
+
if (titleLength > 0 && titleLength < 32768) {
|
|
170
|
+
std::vector<wchar_t> title(static_cast<size_t>(titleLength) + 1u, L'\0');
|
|
171
|
+
const int copied = GetWindowTextW(foreground, title.data(), static_cast<int>(title.size()));
|
|
172
|
+
if (copied > 0) foregroundTitle = Utf8(title.data());
|
|
173
|
+
}
|
|
174
|
+
if (foregroundPid != 0) {
|
|
175
|
+
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, foregroundPid);
|
|
176
|
+
if (process) {
|
|
177
|
+
std::vector<wchar_t> image(32768u, L'\0');
|
|
178
|
+
DWORD chars = static_cast<DWORD>(image.size());
|
|
179
|
+
if (QueryFullProcessImageNameW(process, 0, image.data(), &chars) && chars > 0 && chars < image.size()) {
|
|
180
|
+
image[chars] = L'\0';
|
|
181
|
+
foregroundImage = Utf8(image.data());
|
|
182
|
+
}
|
|
183
|
+
CloseHandle(process);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
DWORD sessionId = 0;
|
|
189
|
+
if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)) sessionId = 0xffffffffu;
|
|
190
|
+
|
|
191
|
+
UINT systemDpi = GetDpiForSystem();
|
|
192
|
+
|
|
193
|
+
std::ostringstream header;
|
|
194
|
+
header << "{";
|
|
195
|
+
header << "\"schema\":\"deadbyte.desktop-native-frame.v1\",";
|
|
196
|
+
header << "\"backend\":\"gdi-bitblt-fallback\",";
|
|
197
|
+
header << "\"pixel_format\":\"BGRA8\",";
|
|
198
|
+
header << "\"left\":" << left << ",";
|
|
199
|
+
header << "\"top\":" << top << ",";
|
|
200
|
+
header << "\"width\":" << width << ",";
|
|
201
|
+
header << "\"height\":" << height << ",";
|
|
202
|
+
header << "\"stride\":" << stride << ",";
|
|
203
|
+
header << "\"byte_length\":" << bytes << ",";
|
|
204
|
+
header << "\"system_dpi\":" << systemDpi << ",";
|
|
205
|
+
header << "\"session_id\":" << sessionId << ",";
|
|
206
|
+
header << "\"input_desktop\":\"" << JsonEscape(inputDesktop) << "\",";
|
|
207
|
+
header << "\"foreground_hwnd\":\"" << HwndHex(foreground) << "\",";
|
|
208
|
+
header << "\"foreground_pid\":" << foregroundPid << ",";
|
|
209
|
+
header << "\"foreground_title\":\"" << JsonEscape(foregroundTitle) << "\",";
|
|
210
|
+
header << "\"foreground_image\":\"" << JsonEscape(foregroundImage) << "\",";
|
|
211
|
+
header << "\"monitors\":[";
|
|
212
|
+
for (size_t i = 0; i < monitors.size(); ++i) {
|
|
213
|
+
if (i) header << ",";
|
|
214
|
+
const auto& m = monitors[i];
|
|
215
|
+
header << "{";
|
|
216
|
+
header << "\"left\":" << m.rect.left << ",";
|
|
217
|
+
header << "\"top\":" << m.rect.top << ",";
|
|
218
|
+
header << "\"width\":" << (m.rect.right - m.rect.left) << ",";
|
|
219
|
+
header << "\"height\":" << (m.rect.bottom - m.rect.top) << ",";
|
|
220
|
+
header << "\"primary\":" << (m.primary ? "true" : "false") << ",";
|
|
221
|
+
header << "\"device\":\"" << JsonEscape(m.device) << "\"";
|
|
222
|
+
header << "}";
|
|
223
|
+
}
|
|
224
|
+
header << "]}";
|
|
225
|
+
|
|
226
|
+
if (_setmode(_fileno(stdout), _O_BINARY) == -1) {
|
|
227
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: stdout binary mode failed\n");
|
|
228
|
+
SelectObject(memory, old);
|
|
229
|
+
DeleteObject(bitmap);
|
|
230
|
+
DeleteDC(memory);
|
|
231
|
+
ReleaseDC(nullptr, screen);
|
|
232
|
+
return 19;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const std::string headerText = header.str();
|
|
236
|
+
if (std::fwrite(headerText.data(), 1, headerText.size(), stdout) != headerText.size() ||
|
|
237
|
+
std::fwrite("\n", 1, 1, stdout) != 1 ||
|
|
238
|
+
std::fwrite(bits, 1, static_cast<size_t>(bytes), stdout) != static_cast<size_t>(bytes) ||
|
|
239
|
+
std::fflush(stdout) != 0) {
|
|
240
|
+
std::fprintf(stderr, "deadbyte-desktop-observer: stdout write failed\n");
|
|
241
|
+
SelectObject(memory, old);
|
|
242
|
+
DeleteObject(bitmap);
|
|
243
|
+
DeleteDC(memory);
|
|
244
|
+
ReleaseDC(nullptr, screen);
|
|
245
|
+
return 20;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
SelectObject(memory, old);
|
|
249
|
+
DeleteObject(bitmap);
|
|
250
|
+
DeleteDC(memory);
|
|
251
|
+
ReleaseDC(nullptr, screen);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#include <windows.h>
|
|
2
|
+
#include <UIAutomation.h>
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include <cerrno>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <cwctype>
|
|
11
|
+
#include <oleauto.h>
|
|
12
|
+
|
|
13
|
+
static constexpr size_t kMaxRequest = 8192;
|
|
14
|
+
static constexpr int kMaxVisited = 2048;
|
|
15
|
+
static constexpr int kMaxResultsHard = 16;
|
|
16
|
+
|
|
17
|
+
struct Match {
|
|
18
|
+
LONG left{};
|
|
19
|
+
LONG top{};
|
|
20
|
+
LONG width{};
|
|
21
|
+
LONG height{};
|
|
22
|
+
int controlType{};
|
|
23
|
+
bool enabled{};
|
|
24
|
+
bool offscreen{};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
struct Request {
|
|
28
|
+
HWND expectedHwnd{};
|
|
29
|
+
DWORD expectedPid{};
|
|
30
|
+
std::string kind;
|
|
31
|
+
int maxResults{8};
|
|
32
|
+
std::string queryUtf8;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
static void fail(const char* message, int code = 2) {
|
|
36
|
+
std::fprintf(stderr, "deadbyte-desktop-uia: %s\n", message);
|
|
37
|
+
std::exit(code);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static bool parseUnsigned(const std::string& text, unsigned long long& out, int base = 10) {
|
|
41
|
+
if (text.empty()) return false;
|
|
42
|
+
char* end = nullptr;
|
|
43
|
+
errno = 0;
|
|
44
|
+
const auto v = std::strtoull(text.c_str(), &end, base);
|
|
45
|
+
if (errno != 0 || end == text.c_str() || *end != '\0') return false;
|
|
46
|
+
out = v;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static std::vector<std::string> readHeaderLines(const std::string& bytes, size_t count, size_t& offset) {
|
|
51
|
+
std::vector<std::string> lines;
|
|
52
|
+
offset = 0;
|
|
53
|
+
for (size_t i = 0; i < count; ++i) {
|
|
54
|
+
const auto pos = bytes.find('\n', offset);
|
|
55
|
+
if (pos == std::string::npos) fail("request header truncated");
|
|
56
|
+
std::string line = bytes.substr(offset, pos - offset);
|
|
57
|
+
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
58
|
+
lines.push_back(line);
|
|
59
|
+
offset = pos + 1;
|
|
60
|
+
}
|
|
61
|
+
return lines;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static std::string field(const std::string& line, const char* key) {
|
|
65
|
+
const std::string prefix = std::string(key) + "=";
|
|
66
|
+
if (line.rfind(prefix, 0) != 0) fail("request field order/name invalid");
|
|
67
|
+
return line.substr(prefix.size());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static Request readRequest() {
|
|
71
|
+
std::string bytes;
|
|
72
|
+
bytes.resize(kMaxRequest + 1);
|
|
73
|
+
const size_t got = std::fread(bytes.data(), 1, bytes.size(), stdin);
|
|
74
|
+
if (std::ferror(stdin)) fail("stdin read failed");
|
|
75
|
+
if (got > kMaxRequest) fail("request exceeds bound");
|
|
76
|
+
bytes.resize(got);
|
|
77
|
+
|
|
78
|
+
size_t offset = 0;
|
|
79
|
+
const auto lines = readHeaderLines(bytes, 6, offset);
|
|
80
|
+
if (lines[0] != "DEADBYTE_DESKTOP_UIA_V1") fail("request magic invalid");
|
|
81
|
+
|
|
82
|
+
Request r;
|
|
83
|
+
const auto hwndText = field(lines[1], "expected_hwnd");
|
|
84
|
+
const auto pidText = field(lines[2], "expected_pid");
|
|
85
|
+
r.kind = field(lines[3], "query_kind");
|
|
86
|
+
const auto maxText = field(lines[4], "max_results");
|
|
87
|
+
const auto queryBytesText = field(lines[5], "query_bytes");
|
|
88
|
+
|
|
89
|
+
unsigned long long hwndValue = 0, pidValue = 0, maxValue = 0, queryBytes = 0;
|
|
90
|
+
std::string h = hwndText;
|
|
91
|
+
if (h.rfind("0x", 0) == 0 || h.rfind("0X", 0) == 0) h = h.substr(2);
|
|
92
|
+
if (!parseUnsigned(h, hwndValue, 16) || hwndValue == 0) fail("expected_hwnd invalid");
|
|
93
|
+
if (!parseUnsigned(pidText, pidValue) || pidValue == 0 || pidValue > 0x7fffffffULL) fail("expected_pid invalid");
|
|
94
|
+
if (!parseUnsigned(maxText, maxValue) || maxValue < 1 || maxValue > kMaxResultsHard) fail("max_results invalid");
|
|
95
|
+
if (!parseUnsigned(queryBytesText, queryBytes) || queryBytes > 512) fail("query_bytes invalid");
|
|
96
|
+
if (offset + queryBytes != bytes.size()) fail("query payload length mismatch");
|
|
97
|
+
|
|
98
|
+
r.expectedHwnd = reinterpret_cast<HWND>(static_cast<uintptr_t>(hwndValue));
|
|
99
|
+
r.expectedPid = static_cast<DWORD>(pidValue);
|
|
100
|
+
r.maxResults = static_cast<int>(maxValue);
|
|
101
|
+
r.queryUtf8.assign(bytes.data() + offset, static_cast<size_t>(queryBytes));
|
|
102
|
+
|
|
103
|
+
const bool needsQuery = r.kind != "root";
|
|
104
|
+
if (needsQuery && r.queryUtf8.empty()) fail("query required");
|
|
105
|
+
if (!needsQuery && !r.queryUtf8.empty()) fail("root query must be empty");
|
|
106
|
+
if (r.kind != "root" && r.kind != "name_exact" && r.kind != "name_contains" &&
|
|
107
|
+
r.kind != "automation_id_exact" && r.kind != "control_type") fail("query_kind invalid");
|
|
108
|
+
return r;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static std::wstring utf8ToWide(const std::string& input) {
|
|
112
|
+
if (input.empty()) return {};
|
|
113
|
+
const int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, input.data(),
|
|
114
|
+
static_cast<int>(input.size()), nullptr, 0);
|
|
115
|
+
if (n <= 0) fail("query UTF-8 invalid");
|
|
116
|
+
std::wstring out(static_cast<size_t>(n), L'\0');
|
|
117
|
+
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, input.data(),
|
|
118
|
+
static_cast<int>(input.size()), out.data(), n) != n) fail("query UTF-8 conversion failed");
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static std::wstring lower(std::wstring s) {
|
|
123
|
+
std::transform(s.begin(), s.end(), s.begin(), [](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
|
|
124
|
+
return s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static int controlTypeFromQuery(const std::string& q) {
|
|
128
|
+
struct Pair { const char* name; int id; };
|
|
129
|
+
static const Pair map[] = {
|
|
130
|
+
{"button", UIA_ButtonControlTypeId}, {"checkbox", UIA_CheckBoxControlTypeId},
|
|
131
|
+
{"combobox", UIA_ComboBoxControlTypeId}, {"edit", UIA_EditControlTypeId},
|
|
132
|
+
{"hyperlink", UIA_HyperlinkControlTypeId}, {"list", UIA_ListControlTypeId},
|
|
133
|
+
{"listitem", UIA_ListItemControlTypeId}, {"menu", UIA_MenuControlTypeId},
|
|
134
|
+
{"menuitem", UIA_MenuItemControlTypeId}, {"radio", UIA_RadioButtonControlTypeId},
|
|
135
|
+
{"tab", UIA_TabControlTypeId}, {"tabitem", UIA_TabItemControlTypeId},
|
|
136
|
+
{"text", UIA_TextControlTypeId}, {"toolbar", UIA_ToolBarControlTypeId},
|
|
137
|
+
{"tree", UIA_TreeControlTypeId}, {"treeitem", UIA_TreeItemControlTypeId},
|
|
138
|
+
{"document", UIA_DocumentControlTypeId}, {"window", UIA_WindowControlTypeId},
|
|
139
|
+
{"pane", UIA_PaneControlTypeId}, {"group", UIA_GroupControlTypeId}
|
|
140
|
+
};
|
|
141
|
+
for (const auto& item : map) if (q == item.name) return item.id;
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static bool matches(IUIAutomationElement* e, const Request& r, const std::wstring& queryWide, int queryControlType) {
|
|
146
|
+
if (r.kind == "root") return true;
|
|
147
|
+
if (r.kind == "control_type") {
|
|
148
|
+
CONTROLTYPEID type = 0;
|
|
149
|
+
return SUCCEEDED(e->get_CurrentControlType(&type)) && type == queryControlType;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
BSTR raw = nullptr;
|
|
153
|
+
HRESULT hr = E_FAIL;
|
|
154
|
+
if (r.kind == "automation_id_exact") hr = e->get_CurrentAutomationId(&raw);
|
|
155
|
+
else hr = e->get_CurrentName(&raw);
|
|
156
|
+
if (FAILED(hr) || raw == nullptr) {
|
|
157
|
+
if (raw) SysFreeString(raw);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
std::wstring value(raw, SysStringLen(raw));
|
|
161
|
+
SysFreeString(raw);
|
|
162
|
+
value = lower(std::move(value));
|
|
163
|
+
const auto query = lower(queryWide);
|
|
164
|
+
if (r.kind == "name_contains") return value.find(query) != std::wstring::npos;
|
|
165
|
+
return value == query;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static bool captureMatch(IUIAutomationElement* e, Match& out) {
|
|
169
|
+
RECT rc{};
|
|
170
|
+
if (FAILED(e->get_CurrentBoundingRectangle(&rc))) return false;
|
|
171
|
+
if (rc.right <= rc.left || rc.bottom <= rc.top) return false;
|
|
172
|
+
BOOL offscreen = TRUE, enabled = FALSE;
|
|
173
|
+
e->get_CurrentIsOffscreen(&offscreen);
|
|
174
|
+
e->get_CurrentIsEnabled(&enabled);
|
|
175
|
+
if (offscreen) return false;
|
|
176
|
+
CONTROLTYPEID type = 0;
|
|
177
|
+
e->get_CurrentControlType(&type);
|
|
178
|
+
out.left = rc.left; out.top = rc.top;
|
|
179
|
+
out.width = rc.right - rc.left; out.height = rc.bottom - rc.top;
|
|
180
|
+
out.controlType = type; out.enabled = enabled != FALSE; out.offscreen = offscreen != FALSE;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static void walk(IUIAutomationTreeWalker* walker, IUIAutomationElement* parent, const Request& req,
|
|
185
|
+
const std::wstring& queryWide, int queryControlType, int depth, int& visited,
|
|
186
|
+
bool& truncated, std::vector<Match>& matchesOut) {
|
|
187
|
+
if (depth > 64 || visited >= kMaxVisited || static_cast<int>(matchesOut.size()) >= req.maxResults) return;
|
|
188
|
+
IUIAutomationElement* child = nullptr;
|
|
189
|
+
if (FAILED(walker->GetFirstChildElement(parent, &child)) || child == nullptr) return;
|
|
190
|
+
while (child != nullptr) {
|
|
191
|
+
++visited;
|
|
192
|
+
if (visited >= kMaxVisited) truncated = true;
|
|
193
|
+
if (matches(child, req, queryWide, queryControlType)) {
|
|
194
|
+
Match m;
|
|
195
|
+
if (captureMatch(child, m)) matchesOut.push_back(m);
|
|
196
|
+
}
|
|
197
|
+
if (!truncated && static_cast<int>(matchesOut.size()) < req.maxResults) {
|
|
198
|
+
walk(walker, child, req, queryWide, queryControlType, depth + 1, visited, truncated, matchesOut);
|
|
199
|
+
}
|
|
200
|
+
IUIAutomationElement* next = nullptr;
|
|
201
|
+
walker->GetNextSiblingElement(child, &next);
|
|
202
|
+
child->Release();
|
|
203
|
+
child = next;
|
|
204
|
+
if (truncated || static_cast<int>(matchesOut.size()) >= req.maxResults) {
|
|
205
|
+
if (child) child->Release();
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static unsigned long long hwndNumber(HWND h) {
|
|
212
|
+
return static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(h));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static int runSelftest() {
|
|
216
|
+
if (controlTypeFromQuery("button") != UIA_ButtonControlTypeId) return 2;
|
|
217
|
+
std::printf("{\"schema\":\"deadbyte.desktop-uia-selftest.v1\",\"status\":\"passed\"}\n");
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
int wmain(int argc, wchar_t** argv) {
|
|
222
|
+
if (argc == 2 && std::wstring(argv[1]) == L"--selftest") return runSelftest();
|
|
223
|
+
const Request req = readRequest();
|
|
224
|
+
|
|
225
|
+
HWND fg = GetForegroundWindow();
|
|
226
|
+
DWORD pid = 0;
|
|
227
|
+
if (fg) GetWindowThreadProcessId(fg, &pid);
|
|
228
|
+
if (fg != req.expectedHwnd || pid != req.expectedPid) fail("foreground window identity changed", 3);
|
|
229
|
+
|
|
230
|
+
const HRESULT init = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
231
|
+
if (FAILED(init) && init != RPC_E_CHANGED_MODE) fail("CoInitializeEx failed", 4);
|
|
232
|
+
const bool uninit = SUCCEEDED(init);
|
|
233
|
+
|
|
234
|
+
IUIAutomation* automation = nullptr;
|
|
235
|
+
HRESULT hr = CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
|
|
236
|
+
if (FAILED(hr) || !automation) {
|
|
237
|
+
if (uninit) CoUninitialize();
|
|
238
|
+
fail("CUIAutomation creation failed", 5);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
IUIAutomationElement* root = nullptr;
|
|
242
|
+
hr = automation->ElementFromHandle(fg, &root);
|
|
243
|
+
if (FAILED(hr) || !root) {
|
|
244
|
+
automation->Release(); if (uninit) CoUninitialize();
|
|
245
|
+
fail("ElementFromHandle failed", 6);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
std::vector<Match> found;
|
|
249
|
+
int visited = 0;
|
|
250
|
+
bool truncated = false;
|
|
251
|
+
const std::wstring queryWide = utf8ToWide(req.queryUtf8);
|
|
252
|
+
const int queryControlType = req.kind == "control_type" ? controlTypeFromQuery(req.queryUtf8) : 0;
|
|
253
|
+
if (req.kind == "control_type" && queryControlType == 0) {
|
|
254
|
+
root->Release(); automation->Release(); if (uninit) CoUninitialize();
|
|
255
|
+
fail("unsupported control_type query", 7);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (req.kind == "root") {
|
|
259
|
+
++visited;
|
|
260
|
+
RECT wr{};
|
|
261
|
+
if (GetWindowRect(fg, &wr)) {
|
|
262
|
+
const LONG vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
263
|
+
const LONG vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
264
|
+
const LONG vr = vx + GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
265
|
+
const LONG vb = vy + GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
266
|
+
const LONG left = (std::max)(wr.left, vx);
|
|
267
|
+
const LONG top = (std::max)(wr.top, vy);
|
|
268
|
+
const LONG right = (std::min)(wr.right, vr);
|
|
269
|
+
const LONG bottom = (std::min)(wr.bottom, vb);
|
|
270
|
+
if (right > left && bottom > top && IsWindowVisible(fg)) {
|
|
271
|
+
Match m;
|
|
272
|
+
m.left = left; m.top = top; m.width = right - left; m.height = bottom - top;
|
|
273
|
+
m.controlType = UIA_WindowControlTypeId;
|
|
274
|
+
m.enabled = IsWindowEnabled(fg) != FALSE;
|
|
275
|
+
m.offscreen = false;
|
|
276
|
+
found.push_back(m);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
IUIAutomationTreeWalker* walker = nullptr;
|
|
281
|
+
hr = automation->get_ControlViewWalker(&walker);
|
|
282
|
+
if (FAILED(hr) || !walker) {
|
|
283
|
+
root->Release(); automation->Release(); if (uninit) CoUninitialize();
|
|
284
|
+
fail("ControlViewWalker unavailable", 8);
|
|
285
|
+
}
|
|
286
|
+
walk(walker, root, req, queryWide, queryControlType, 0, visited, truncated, found);
|
|
287
|
+
walker->Release();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
std::printf("{\"schema\":\"deadbyte.desktop-uia-ground.v1\",\"foreground_hwnd\":\"0x%llx\",\"foreground_pid\":%lu,"
|
|
291
|
+
"\"query_kind\":\"%s\",\"visited\":%d,\"truncated\":%s,\"matches\":[",
|
|
292
|
+
hwndNumber(fg), static_cast<unsigned long>(pid), req.kind.c_str(), visited, truncated ? "true" : "false");
|
|
293
|
+
for (size_t i = 0; i < found.size(); ++i) {
|
|
294
|
+
if (i) std::printf(",");
|
|
295
|
+
const auto& m = found[i];
|
|
296
|
+
std::printf("{\"left\":%ld,\"top\":%ld,\"width\":%ld,\"height\":%ld,\"control_type\":%d,"
|
|
297
|
+
"\"enabled\":%s,\"offscreen\":%s}",
|
|
298
|
+
m.left, m.top, m.width, m.height, m.controlType,
|
|
299
|
+
m.enabled ? "true" : "false", m.offscreen ? "true" : "false");
|
|
300
|
+
}
|
|
301
|
+
std::printf("]}\n");
|
|
302
|
+
|
|
303
|
+
root->Release();
|
|
304
|
+
automation->Release();
|
|
305
|
+
if (uninit) CoUninitialize();
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deadbyte-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Authenticated MCP machine-truth runtime with signed audit chain, immutable release slots, contained execution, coding/autonomous production authority, and npx-first remote control.",
|
|
6
|
+
"description": "Authenticated MCP machine-truth runtime with signed audit chain, immutable release slots, contained execution, coding/autonomous production authority, guarded Windows Desktop computer-use, and npx-first remote control.",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22"
|
|
9
9
|
},
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"manifest:write": "node scripts/release-manifest.mjs write",
|
|
13
13
|
"manifest:verify": "node scripts/release-manifest.mjs verify",
|
|
14
14
|
"self-update:runtime": "node scripts/runtime-self-update.mjs",
|
|
15
|
-
"self-update:deferred": "node scripts/deferred-slot-operation-
|
|
16
|
-
"release:parity": "node scripts/release-parity-
|
|
17
|
-
"release:final:write": "node scripts/final-closure-
|
|
18
|
-
"release:final:verify": "node scripts/final-closure-verify-
|
|
15
|
+
"self-update:deferred": "node scripts/deferred-slot-operation-r40.mjs",
|
|
16
|
+
"release:parity": "node scripts/release-parity-r40.mjs",
|
|
17
|
+
"release:final:write": "node scripts/final-closure-r40.mjs",
|
|
18
|
+
"release:final:verify": "node scripts/final-closure-verify-r40.mjs",
|
|
19
19
|
"proof": "node src/proof-cli.mjs workspaces/demo input.txt",
|
|
20
20
|
"verify": "node src/verifier-cli.mjs workspaces/demo evidence/latest.json",
|
|
21
21
|
"trust:init": "node src/trust-init.mjs",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"contained:run": "node src/contained-cli.mjs workspaces/demo input.txt",
|
|
39
39
|
"contained:smoke": "node src/contained-smoke.mjs",
|
|
40
40
|
"mcp:contained:smoke": "node src/mcp-contained-smoke-client.mjs",
|
|
41
|
-
"gate:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-
|
|
41
|
+
"gate:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r40.ps1",
|
|
42
42
|
"authority:smoke": "node src/authority-smoke.mjs",
|
|
43
43
|
"mcp:remote:smoke": "node src/remote-mcp-smoke-client.mjs",
|
|
44
44
|
"mcp:host:smoke": "node src/mcp-host-smoke-client.mjs",
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
"mcp:remote:autonomous:smoke": "node src/remote-mcp-autonomous-smoke-client.mjs",
|
|
48
48
|
"mcp:tunnel-compat:smoke": "node src/tunnel-stdio-compat-smoke.mjs",
|
|
49
49
|
"audit:verify": "node src/deadbyte-cli.mjs audit verify",
|
|
50
|
-
"audit:status": "node src/deadbyte-cli.mjs audit status"
|
|
50
|
+
"audit:status": "node src/deadbyte-cli.mjs audit status",
|
|
51
|
+
"desktop:build": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-desktop-r40.ps1",
|
|
52
|
+
"desktop:policy:refresh": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/init-desktop-policy-r40.ps1"
|
|
51
53
|
},
|
|
52
54
|
"bundleDependencies": [
|
|
53
55
|
"@modelcontextprotocol/server",
|