deadbyte-mcp 0.15.74 → 0.15.76
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 +108 -42
- package/PROVENANCE.json +20 -22
- package/README.txt +23 -18
- package/RELEASE.json +69 -18
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +32 -2
- package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
- package/bin/deadbyte-desktop-uia-r199.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199.obj +0 -0
- package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
- package/bin/deadbyte-fs-read-r198.exe +0 -0
- package/bin/deadbyte-fs-read-r198.obj +0 -0
- package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +2 -8
- package/native/deadbyte-desktop-uia-r199.cpp +345 -0
- package/native/deadbyte-fs-read-r197.cpp +174 -0
- package/native/deadbyte-fs-read-r198.cpp +181 -0
- package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
- package/package.json +5 -2
- package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
- package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
- package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
- package/scripts/build-native-fs-read-r197.ps1 +28 -0
- package/scripts/build-native-fs-read-r198.ps1 +28 -0
- package/scripts/build-native-root-probe-r197.ps1 +28 -0
- package/scripts/gate-windows-r195.ps1 +114 -0
- package/scripts/gate-windows-r206.ps1 +116 -0
- package/scripts/gate-windows-r207.ps1 +104 -0
- package/scripts/init-desktop-policy-r199.ps1 +85 -0
- package/scripts/npm-release-r48.mjs +27 -0
- package/scripts/npm-release-readiness-r195.mjs +72 -0
- package/scripts/npm-release-readiness-r206.mjs +76 -0
- package/scripts/npm-release-readiness-r207.mjs +76 -0
- package/scripts/r193-owner-request-live.mjs +8 -7
- package/scripts/r193-owner-uac-broker.mjs +6 -5
- package/scripts/r205-candidate-selection.mjs +22 -0
- package/src/candidate-selection-evidence-r206.mjs +71 -0
- package/src/candidate-selection-r205.mjs +96 -0
- package/src/desktop-postcondition-r196.mjs +30 -0
- package/src/desktop-runtime-r40.mjs +44 -7
- package/src/desktop-uia-identity-r199.mjs +51 -0
- package/src/desktop-uia-r40.mjs +9 -1
- package/src/machine-fs-handle-read-r196.mjs +43 -0
- package/src/machine-fs-native-read-r197.mjs +73 -0
- package/src/machine-fs-native-read-r198.mjs +76 -0
- package/src/machine-fs-read-worker.mjs +5 -25
- package/src/machine-fs.mjs +31 -16
- package/src/r195-canonical-owner-observation.mjs +15 -0
- package/src/r207-owner-enrollment-io.mjs +30 -0
- package/src/remote-destination-journal-r202.mjs +120 -0
- package/src/remote-device-r44.mjs +89 -14
- package/src/remote-payload-boundary-r201.mjs +71 -0
- package/src/remote-terminal-evidence-r200.mjs +60 -0
- package/src/version.mjs +1 -1
- package/test/desktop-runtime-r40.test.mjs +111 -0
- package/test/desktop-uia-r40.test.mjs +15 -0
- package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
- package/test/fixtures/r203-journal-writer.mjs +6 -0
- package/test/process-policy-refresh.test.mjs +4 -4
- package/test/r130-release-security-contract.test.mjs +8 -8
- package/test/r132-release-identity.test.mjs +7 -7
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +3 -3
- package/test/r165-live-cli-skew.test.mjs +1 -1
- package/test/r167-release-contract.test.mjs +11 -11
- package/test/r168-release-contract.test.mjs +4 -4
- package/test/r172-release-contract.test.mjs +6 -6
- package/test/r173-release-contract.test.mjs +16 -16
- package/test/r179-release-contract.test.mjs +12 -12
- package/test/r190-release-contract.test.mjs +10 -10
- package/test/r191-release-contract.test.mjs +12 -12
- package/test/r192-release-identity.test.mjs +9 -9
- package/test/r193-release-identity.test.mjs +24 -22
- package/test/r193-release-readiness.test.mjs +2 -2
- package/test/r193-windows-gate.test.mjs +10 -10
- package/test/r194-release-identity.test.mjs +19 -15
- package/test/r194-release-readiness.test.mjs +3 -3
- package/test/r194-windows-gate.test.mjs +9 -8
- package/test/r195-canonical-owner-observation.test.mjs +32 -0
- package/test/r195-release-readiness.test.mjs +22 -0
- package/test/r196-desktop-postcondition.test.mjs +53 -0
- package/test/r196-handle-bound-read.test.mjs +99 -0
- package/test/r196-remote-outcome.test.mjs +94 -0
- package/test/r197-namespace-race.test.mjs +41 -0
- package/test/r197-native-dispatch-race.test.mjs +49 -0
- package/test/r197-native-file-read.test.mjs +60 -0
- package/test/r197-native-root-open.test.mjs +61 -0
- package/test/r198-root-identity.test.mjs +88 -0
- package/test/r199-native-uia-worker.test.mjs +28 -0
- package/test/r199-uia-identity.test.mjs +43 -0
- package/test/r200-remote-reconcile.test.mjs +106 -0
- package/test/r201-payload-boundary.test.mjs +78 -0
- package/test/r201-payload-fuzz.test.mjs +37 -0
- package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
- package/test/r202-destination-journal.test.mjs +92 -0
- package/test/r203-atomic-journal.test.mjs +86 -0
- package/test/r205-candidate-selection-cli.test.mjs +31 -0
- package/test/r205-candidate-selection.test.mjs +83 -0
- package/test/r206-candidate-evidence.test.mjs +64 -0
- package/test/r206-release-contract.test.mjs +42 -0
- package/test/r206-release-readiness.test.mjs +23 -0
- package/test/r207-owner-enrollment-io.test.mjs +37 -0
- package/test/r207-release-contract.test.mjs +51 -0
- package/test/r207-release-readiness.test.mjs +23 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-machine-fs-large-read.test.mjs +26 -12
package/RELEASE.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "deadbyte.release.v1",
|
|
3
3
|
"name": "deadbyte-mcp",
|
|
4
|
-
"version": "0.15.
|
|
5
|
-
"train": "
|
|
6
|
-
"label": "
|
|
4
|
+
"version": "0.15.76",
|
|
5
|
+
"train": "R207",
|
|
6
|
+
"label": "R207 protected owner-enrollment IO recovery",
|
|
7
7
|
"protocol_revision": "2026-07-28",
|
|
8
8
|
"parent": {
|
|
9
|
-
"version": "0.15.
|
|
10
|
-
"release_id": "v0.15.
|
|
11
|
-
"manifest_sha256": "
|
|
12
|
-
"generation_manifest_sha256": "
|
|
9
|
+
"version": "0.15.75",
|
|
10
|
+
"release_id": "v0.15.75-9039fff265696091",
|
|
11
|
+
"manifest_sha256": "9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248",
|
|
12
|
+
"generation_manifest_sha256": "83a212e01173c73f78cff2a6d26e6f3f50f476a8c0ea3cbbf592a466071909e9",
|
|
13
13
|
"activation_status": "PRODUCTION_ACTIVE"
|
|
14
14
|
},
|
|
15
15
|
"runtime_predecessor": {
|
|
16
|
-
"version": "0.15.
|
|
17
|
-
"release_id": "v0.15.
|
|
18
|
-
"manifest_sha256": "
|
|
19
|
-
"generation_manifest_sha256": "
|
|
16
|
+
"version": "0.15.75",
|
|
17
|
+
"release_id": "v0.15.75-9039fff265696091",
|
|
18
|
+
"manifest_sha256": "9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248",
|
|
19
|
+
"generation_manifest_sha256": "83a212e01173c73f78cff2a6d26e6f3f50f476a8c0ea3cbbf592a466071909e9"
|
|
20
20
|
},
|
|
21
21
|
"r190_foreground_recovery": {
|
|
22
22
|
"status": "PREPUBLISH_VERIFIED",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"authority_scope_delta": 0
|
|
37
37
|
},
|
|
38
38
|
"source_predecessor": {
|
|
39
|
-
"version": "0.15.
|
|
40
|
-
"train": "
|
|
41
|
-
"source_path": "D:\\Projects\\deadbyte-mcp\\
|
|
42
|
-
"release_status": "
|
|
43
|
-
"manifest_status": "
|
|
44
|
-
"manifest_sha256": "
|
|
45
|
-
"tarball_sha256": "
|
|
39
|
+
"version": "0.15.75",
|
|
40
|
+
"train": "R206",
|
|
41
|
+
"source_path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.75-r206-release-closure",
|
|
42
|
+
"release_status": "RELEASED",
|
|
43
|
+
"manifest_status": "R206_MANIFEST_VERIFIED",
|
|
44
|
+
"manifest_sha256": "9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248",
|
|
45
|
+
"tarball_sha256": "c2ebb079e7725f3a2f2c7d8f98ef42059e94e51f402d99c1825f46e020237ee9"
|
|
46
46
|
},
|
|
47
47
|
"consolidation": {
|
|
48
48
|
"frontend_status_vocabulary": true,
|
|
@@ -671,5 +671,56 @@
|
|
|
671
671
|
"limited_runtime_migration": "NOT_RUN",
|
|
672
672
|
"npm_publish": "NOT_RUN",
|
|
673
673
|
"production_activation": "NOT_RUN"
|
|
674
|
+
},
|
|
675
|
+
"r195": {
|
|
676
|
+
"status": "BEHAVIOR_GREEN_RELEASE_GATES_PENDING",
|
|
677
|
+
"scope": "CANONICAL_PROTECTED_OWNER_OBSERVATION",
|
|
678
|
+
"development_policy_root": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.75-r195-owner-enrollment-live",
|
|
679
|
+
"source_predecessor_version": "0.15.74",
|
|
680
|
+
"source_predecessor_manifest_sha256": "e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47",
|
|
681
|
+
"source_predecessor_tarball_sha256": "5da436a3f8068107e74caf6da58ff1e211d4a62f94266853136b601762f36f0c",
|
|
682
|
+
"public_tool_delta": 0,
|
|
683
|
+
"authority_scope_delta": 0,
|
|
684
|
+
"owner_enrollment": "PENDING_OWNER_GESTURE_AFTER_ACTIVATION",
|
|
685
|
+
"npm_publish": "NOT_RUN",
|
|
686
|
+
"production_activation": "NOT_RUN"
|
|
687
|
+
},
|
|
688
|
+
"r206": {
|
|
689
|
+
"status": "BEHAVIOR_GREEN_FINAL_GATES_PENDING",
|
|
690
|
+
"scope": "EVIDENCE_BOUND_CANDIDATE_SELECTION_RELEASE_CLOSURE",
|
|
691
|
+
"development_policy_root": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.75-r206-release-closure",
|
|
692
|
+
"source_predecessor_train": "R205",
|
|
693
|
+
"source_predecessor_manifest_sha256": "166eebde18d53a9ec46f738383c289733f9cd37918b3572b7515d1ab9b122c92",
|
|
694
|
+
"runtime_predecessor_release_id": "v0.15.74-e84bf4eed16e9443",
|
|
695
|
+
"selector_release_authority": false,
|
|
696
|
+
"public_tool_delta": 0,
|
|
697
|
+
"authority_scope_delta": 0,
|
|
698
|
+
"deferred_unverified": [
|
|
699
|
+
"REMOTE_TWO_MACHINE_E2E",
|
|
700
|
+
"NATIVE_UIA_APPLICATION_EFFECT",
|
|
701
|
+
"BOUNDED_CDP_HARNESS2",
|
|
702
|
+
"REA_WINDOWS_209_TOOL_PARITY"
|
|
703
|
+
],
|
|
704
|
+
"owner_enrollment": "PENDING_SEPARATE_POST_ACTIVATION_OWNER_GESTURE",
|
|
705
|
+
"npm_publish": "NOT_RUN",
|
|
706
|
+
"production_activation": "NOT_RUN"
|
|
707
|
+
},
|
|
708
|
+
"r207": {
|
|
709
|
+
"status": "BEHAVIOR_GREEN_FINAL_GATES_PENDING",
|
|
710
|
+
"scope": "PROTECTED_OWNER_ENROLLMENT_IO_RECOVERY",
|
|
711
|
+
"development_policy_root": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.76-r207-owner-enrollment-fix",
|
|
712
|
+
"source_predecessor_train": "R206",
|
|
713
|
+
"source_predecessor_manifest_sha256": "9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248",
|
|
714
|
+
"runtime_predecessor_release_id": "v0.15.75-9039fff265696091",
|
|
715
|
+
"owner_consent_directory_initialization": "FAIL_CLOSED_CONTAINED_NON_REPARSE",
|
|
716
|
+
"supervisor_ledger_utf8_bom": "ACCEPT_ONE_LEADING_BOM_ONLY",
|
|
717
|
+
"owner_private_key_access": false,
|
|
718
|
+
"r70_weakened": false,
|
|
719
|
+
"r89_weakened": false,
|
|
720
|
+
"public_tool_delta": 0,
|
|
721
|
+
"authority_scope_delta": 0,
|
|
722
|
+
"owner_enrollment": "PENDING_OWNER_GESTURE_AFTER_ACTIVATION",
|
|
723
|
+
"npm_publish": "NOT_RUN",
|
|
724
|
+
"production_activation": "NOT_RUN"
|
|
674
725
|
}
|
|
675
726
|
}
|
package/SBOM.cdx.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:cdx:deadbyte-mcp:
|
|
4
|
+
"serialNumber": "urn:cdx:deadbyte-mcp:r207:0.15.76",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
7
|
"component": {
|
|
8
8
|
"type": "application",
|
|
9
9
|
"name": "deadbyte-mcp",
|
|
10
|
-
"version": "0.15.
|
|
10
|
+
"version": "0.15.76"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"components": [
|
package/TEST-MATRIX.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "deadbyte.test-matrix.v1",
|
|
3
|
-
"version": "0.15.
|
|
4
|
-
"train": "
|
|
3
|
+
"version": "0.15.76",
|
|
4
|
+
"train": "R207",
|
|
5
5
|
"primary_platform": "win32-x64",
|
|
6
6
|
"reference": {
|
|
7
7
|
"package": "@wonderwhy-er/desktop-commander",
|
|
@@ -10,6 +10,36 @@
|
|
|
10
10
|
"schemas_sha256": "f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140"
|
|
11
11
|
},
|
|
12
12
|
"required": [
|
|
13
|
+
{
|
|
14
|
+
"id": "r207-owner-enrollment-io",
|
|
15
|
+
"command": "node --test --test-concurrency=1 test/r207-owner-enrollment-io.test.mjs test/r193-owner-uac-broker.test.mjs test/r193-owner-uac-entrypoint.test.mjs",
|
|
16
|
+
"expected": "Protected owner request creates only its contained non-reparse state directory and the broker accepts exactly one UTF-8 BOM without weakening proof checks"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "r207-release-identity",
|
|
20
|
+
"command": "node --test --test-concurrency=1 test/r207-release-contract.test.mjs",
|
|
21
|
+
"expected": "R206 source and active 0.15.75 runtime predecessors remain distinct; public tool and authority surfaces are unchanged"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "r206-independent-candidate-evidence",
|
|
25
|
+
"command": "node --test --test-concurrency=1 test/r206-candidate-evidence.test.mjs",
|
|
26
|
+
"expected": "Every decision-bearing correctness and benchmark receipt is independently verified and the selector remains unable to authorize release"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "r206-release-identity",
|
|
30
|
+
"command": "node --test --test-concurrency=1 test/r206-release-contract.test.mjs",
|
|
31
|
+
"expected": "R205 source and active 0.15.74 runtime predecessors remain distinct; public tool and authority surfaces are unchanged"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "r206-protected-full-and-package",
|
|
35
|
+
"command": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r206.ps1 -Stage all",
|
|
36
|
+
"expected": "Exact final manifest passes R89 protected focused/full regression and package verify-only with every skip classified"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "r195-canonical-protected-owner-observation",
|
|
40
|
+
"command": "node --test test/r195-canonical-owner-observation.test.mjs",
|
|
41
|
+
"expected": "Protected owner request and broker observe the exact active controller through the canonical verified launcher"
|
|
42
|
+
},
|
|
13
43
|
{
|
|
14
44
|
"id": "r191-trusted-successor-epoch-root",
|
|
15
45
|
"command": "run R191 protected 0.15.71 ACL and full Windows regression",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bridge/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "deadbyte.process-policy.v1",
|
|
3
3
|
"enabled": true,
|
|
4
|
-
"grant_file": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
5
|
-
"state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
4
|
+
"grant_file": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.76-r207-owner-enrollment-fix\\controller\\runtime\\process-grant.json",
|
|
5
|
+
"state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.76-r207-owner-enrollment-fix\\controller\\runtime\\process-state",
|
|
6
6
|
"native_host": {
|
|
7
|
-
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
7
|
+
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.76-r207-owner-enrollment-fix\\bin\\deadbyte-process-host.exe",
|
|
8
8
|
"sha256": "daf9cfb371bee88cafbf5597eaa507fec504dbe0b0ddd40d0c3c2e02817f49e4"
|
|
9
9
|
},
|
|
10
10
|
"roots": {
|
|
11
11
|
"core": {
|
|
12
|
-
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
12
|
+
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.76-r207-owner-enrollment-fix",
|
|
13
13
|
"deny_paths": [
|
|
14
14
|
"node_modules",
|
|
15
15
|
"controller/runtime",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deadbyte-mcp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.76",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "deadbyte-mcp",
|
|
9
|
-
"version": "0.15.
|
|
9
|
+
"version": "0.15.76",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"@modelcontextprotocol/server",
|
|
12
12
|
"@modelcontextprotocol/client",
|
|
@@ -28,8 +28,6 @@
|
|
|
28
28
|
},
|
|
29
29
|
"node_modules/@modelcontextprotocol/client": {
|
|
30
30
|
"version": "2.3.1",
|
|
31
|
-
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.3.1.tgz",
|
|
32
|
-
"integrity": "sha512-mIGZXpHsjnZ6lD+gD/WCMpR5k8yVQQ8nNFH1N0Srf7AvnwTUMYD6pvTY8ng2+He5FfV7YMZLmrjL7cLa/cc3dQ==",
|
|
33
31
|
"inBundle": true,
|
|
34
32
|
"license": "Apache-2.0",
|
|
35
33
|
"dependencies": {
|
|
@@ -47,8 +45,6 @@
|
|
|
47
45
|
},
|
|
48
46
|
"node_modules/@modelcontextprotocol/core": {
|
|
49
47
|
"version": "2.3.1",
|
|
50
|
-
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz",
|
|
51
|
-
"integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==",
|
|
52
48
|
"inBundle": true,
|
|
53
49
|
"license": "Apache-2.0",
|
|
54
50
|
"dependencies": {
|
|
@@ -60,8 +56,6 @@
|
|
|
60
56
|
},
|
|
61
57
|
"node_modules/@modelcontextprotocol/server": {
|
|
62
58
|
"version": "2.3.1",
|
|
63
|
-
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz",
|
|
64
|
-
"integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==",
|
|
65
59
|
"inBundle": true,
|
|
66
60
|
"license": "Apache-2.0",
|
|
67
61
|
"dependencies": {
|
|
@@ -0,0 +1,345 @@
|
|
|
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
|
+
std::vector<LONG> runtimeId;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
struct Request {
|
|
29
|
+
HWND expectedHwnd{};
|
|
30
|
+
DWORD expectedPid{};
|
|
31
|
+
std::string kind;
|
|
32
|
+
int maxResults{8};
|
|
33
|
+
std::string queryUtf8;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
static void fail(const char* message, int code = 2) {
|
|
37
|
+
std::fprintf(stderr, "deadbyte-desktop-uia: %s\n", message);
|
|
38
|
+
std::exit(code);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static bool parseUnsigned(const std::string& text, unsigned long long& out, int base = 10) {
|
|
42
|
+
if (text.empty()) return false;
|
|
43
|
+
char* end = nullptr;
|
|
44
|
+
errno = 0;
|
|
45
|
+
const auto v = std::strtoull(text.c_str(), &end, base);
|
|
46
|
+
if (errno != 0 || end == text.c_str() || *end != '\0') return false;
|
|
47
|
+
out = v;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static std::vector<std::string> readHeaderLines(const std::string& bytes, size_t count, size_t& offset) {
|
|
52
|
+
std::vector<std::string> lines;
|
|
53
|
+
offset = 0;
|
|
54
|
+
for (size_t i = 0; i < count; ++i) {
|
|
55
|
+
const auto pos = bytes.find('\n', offset);
|
|
56
|
+
if (pos == std::string::npos) fail("request header truncated");
|
|
57
|
+
std::string line = bytes.substr(offset, pos - offset);
|
|
58
|
+
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
59
|
+
lines.push_back(line);
|
|
60
|
+
offset = pos + 1;
|
|
61
|
+
}
|
|
62
|
+
return lines;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static std::string field(const std::string& line, const char* key) {
|
|
66
|
+
const std::string prefix = std::string(key) + "=";
|
|
67
|
+
if (line.rfind(prefix, 0) != 0) fail("request field order/name invalid");
|
|
68
|
+
return line.substr(prefix.size());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static Request readRequest() {
|
|
72
|
+
std::string bytes;
|
|
73
|
+
bytes.resize(kMaxRequest + 1);
|
|
74
|
+
const size_t got = std::fread(bytes.data(), 1, bytes.size(), stdin);
|
|
75
|
+
if (std::ferror(stdin)) fail("stdin read failed");
|
|
76
|
+
if (got > kMaxRequest) fail("request exceeds bound");
|
|
77
|
+
bytes.resize(got);
|
|
78
|
+
|
|
79
|
+
size_t offset = 0;
|
|
80
|
+
const auto lines = readHeaderLines(bytes, 6, offset);
|
|
81
|
+
if (lines[0] != "DEADBYTE_DESKTOP_UIA_V1") fail("request magic invalid");
|
|
82
|
+
|
|
83
|
+
Request r;
|
|
84
|
+
const auto hwndText = field(lines[1], "expected_hwnd");
|
|
85
|
+
const auto pidText = field(lines[2], "expected_pid");
|
|
86
|
+
r.kind = field(lines[3], "query_kind");
|
|
87
|
+
const auto maxText = field(lines[4], "max_results");
|
|
88
|
+
const auto queryBytesText = field(lines[5], "query_bytes");
|
|
89
|
+
|
|
90
|
+
unsigned long long hwndValue = 0, pidValue = 0, maxValue = 0, queryBytes = 0;
|
|
91
|
+
std::string h = hwndText;
|
|
92
|
+
if (h.rfind("0x", 0) == 0 || h.rfind("0X", 0) == 0) h = h.substr(2);
|
|
93
|
+
if (!parseUnsigned(h, hwndValue, 16) || hwndValue == 0) fail("expected_hwnd invalid");
|
|
94
|
+
if (!parseUnsigned(pidText, pidValue) || pidValue == 0 || pidValue > 0x7fffffffULL) fail("expected_pid invalid");
|
|
95
|
+
if (!parseUnsigned(maxText, maxValue) || maxValue < 1 || maxValue > kMaxResultsHard) fail("max_results invalid");
|
|
96
|
+
if (!parseUnsigned(queryBytesText, queryBytes) || queryBytes > 512) fail("query_bytes invalid");
|
|
97
|
+
if (offset + queryBytes != bytes.size()) fail("query payload length mismatch");
|
|
98
|
+
|
|
99
|
+
r.expectedHwnd = reinterpret_cast<HWND>(static_cast<uintptr_t>(hwndValue));
|
|
100
|
+
r.expectedPid = static_cast<DWORD>(pidValue);
|
|
101
|
+
r.maxResults = static_cast<int>(maxValue);
|
|
102
|
+
r.queryUtf8.assign(bytes.data() + offset, static_cast<size_t>(queryBytes));
|
|
103
|
+
|
|
104
|
+
const bool needsQuery = r.kind != "root";
|
|
105
|
+
if (needsQuery && r.queryUtf8.empty()) fail("query required");
|
|
106
|
+
if (!needsQuery && !r.queryUtf8.empty()) fail("root query must be empty");
|
|
107
|
+
if (r.kind != "root" && r.kind != "name_exact" && r.kind != "name_contains" &&
|
|
108
|
+
r.kind != "automation_id_exact" && r.kind != "control_type") fail("query_kind invalid");
|
|
109
|
+
return r;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static std::wstring utf8ToWide(const std::string& input) {
|
|
113
|
+
if (input.empty()) return {};
|
|
114
|
+
const int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, input.data(),
|
|
115
|
+
static_cast<int>(input.size()), nullptr, 0);
|
|
116
|
+
if (n <= 0) fail("query UTF-8 invalid");
|
|
117
|
+
std::wstring out(static_cast<size_t>(n), L'\0');
|
|
118
|
+
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, input.data(),
|
|
119
|
+
static_cast<int>(input.size()), out.data(), n) != n) fail("query UTF-8 conversion failed");
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static std::wstring lower(std::wstring s) {
|
|
124
|
+
std::transform(s.begin(), s.end(), s.begin(), [](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
|
|
125
|
+
return s;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static int controlTypeFromQuery(const std::string& q) {
|
|
129
|
+
struct Pair { const char* name; int id; };
|
|
130
|
+
static const Pair map[] = {
|
|
131
|
+
{"button", UIA_ButtonControlTypeId}, {"checkbox", UIA_CheckBoxControlTypeId},
|
|
132
|
+
{"combobox", UIA_ComboBoxControlTypeId}, {"edit", UIA_EditControlTypeId},
|
|
133
|
+
{"hyperlink", UIA_HyperlinkControlTypeId}, {"list", UIA_ListControlTypeId},
|
|
134
|
+
{"listitem", UIA_ListItemControlTypeId}, {"menu", UIA_MenuControlTypeId},
|
|
135
|
+
{"menuitem", UIA_MenuItemControlTypeId}, {"radio", UIA_RadioButtonControlTypeId},
|
|
136
|
+
{"tab", UIA_TabControlTypeId}, {"tabitem", UIA_TabItemControlTypeId},
|
|
137
|
+
{"text", UIA_TextControlTypeId}, {"toolbar", UIA_ToolBarControlTypeId},
|
|
138
|
+
{"tree", UIA_TreeControlTypeId}, {"treeitem", UIA_TreeItemControlTypeId},
|
|
139
|
+
{"document", UIA_DocumentControlTypeId}, {"window", UIA_WindowControlTypeId},
|
|
140
|
+
{"pane", UIA_PaneControlTypeId}, {"group", UIA_GroupControlTypeId}
|
|
141
|
+
};
|
|
142
|
+
for (const auto& item : map) if (q == item.name) return item.id;
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static bool matches(IUIAutomationElement* e, const Request& r, const std::wstring& queryWide, int queryControlType) {
|
|
147
|
+
if (r.kind == "root") return true;
|
|
148
|
+
if (r.kind == "control_type") {
|
|
149
|
+
CONTROLTYPEID type = 0;
|
|
150
|
+
return SUCCEEDED(e->get_CurrentControlType(&type)) && type == queryControlType;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
BSTR raw = nullptr;
|
|
154
|
+
HRESULT hr = E_FAIL;
|
|
155
|
+
if (r.kind == "automation_id_exact") hr = e->get_CurrentAutomationId(&raw);
|
|
156
|
+
else hr = e->get_CurrentName(&raw);
|
|
157
|
+
if (FAILED(hr) || raw == nullptr) {
|
|
158
|
+
if (raw) SysFreeString(raw);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
std::wstring value(raw, SysStringLen(raw));
|
|
162
|
+
SysFreeString(raw);
|
|
163
|
+
value = lower(std::move(value));
|
|
164
|
+
const auto query = lower(queryWide);
|
|
165
|
+
if (r.kind == "name_contains") return value.find(query) != std::wstring::npos;
|
|
166
|
+
return value == query;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// RuntimeId is an ephemeral bounded value, not a long-lived COM reference.
|
|
170
|
+
static bool readRuntimeId(IUIAutomationElement* e, std::vector<LONG>& out) {
|
|
171
|
+
SAFEARRAY* ids = nullptr;
|
|
172
|
+
const HRESULT identityStatus = e->GetRuntimeId(&ids);
|
|
173
|
+
if (FAILED(identityStatus) || !ids) {
|
|
174
|
+
if (ids) SafeArrayDestroy(ids);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const UINT dims = SafeArrayGetDim(ids);
|
|
178
|
+
LONG low = 0, high = -1;
|
|
179
|
+
const bool boundsOk = dims == 1 &&
|
|
180
|
+
SUCCEEDED(SafeArrayGetLBound(ids, 1, &low)) &&
|
|
181
|
+
SUCCEEDED(SafeArrayGetUBound(ids, 1, &high));
|
|
182
|
+
const long long count = boundsOk ? static_cast<long long>(high) -
|
|
183
|
+
static_cast<long long>(low) + 1 : 0;
|
|
184
|
+
if (!boundsOk || count < 1 || count > 64) {
|
|
185
|
+
SafeArrayDestroy(ids); return false;
|
|
186
|
+
}
|
|
187
|
+
std::vector<LONG> candidate;
|
|
188
|
+
candidate.reserve(static_cast<size_t>(count));
|
|
189
|
+
for (long long j = 0; j < count; ++j) {
|
|
190
|
+
LONG i = static_cast<LONG>(static_cast<long long>(low) + j);
|
|
191
|
+
LONG value = 0;
|
|
192
|
+
if (FAILED(SafeArrayGetElement(ids, &i, &value))) {
|
|
193
|
+
SafeArrayDestroy(ids); return false;
|
|
194
|
+
}
|
|
195
|
+
candidate.push_back(value);
|
|
196
|
+
}
|
|
197
|
+
SafeArrayDestroy(ids);
|
|
198
|
+
out = std::move(candidate);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
static bool captureMatch(IUIAutomationElement* e, Match& out) {
|
|
202
|
+
RECT rc{};
|
|
203
|
+
if (FAILED(e->get_CurrentBoundingRectangle(&rc))) return false;
|
|
204
|
+
if (rc.right <= rc.left || rc.bottom <= rc.top) return false;
|
|
205
|
+
BOOL offscreen = TRUE, enabled = FALSE;
|
|
206
|
+
e->get_CurrentIsOffscreen(&offscreen);
|
|
207
|
+
e->get_CurrentIsEnabled(&enabled);
|
|
208
|
+
if (offscreen) return false;
|
|
209
|
+
CONTROLTYPEID type = 0;
|
|
210
|
+
e->get_CurrentControlType(&type);
|
|
211
|
+
out.left = rc.left; out.top = rc.top;
|
|
212
|
+
out.width = rc.right - rc.left; out.height = rc.bottom - rc.top;
|
|
213
|
+
out.controlType = type; out.enabled = enabled != FALSE; out.offscreen = offscreen != FALSE;
|
|
214
|
+
return readRuntimeId(e, out.runtimeId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static void walk(IUIAutomationTreeWalker* walker, IUIAutomationElement* parent, const Request& req,
|
|
218
|
+
const std::wstring& queryWide, int queryControlType, int depth, int& visited,
|
|
219
|
+
bool& truncated, std::vector<Match>& matchesOut) {
|
|
220
|
+
if (depth > 64 || visited >= kMaxVisited || static_cast<int>(matchesOut.size()) >= req.maxResults) return;
|
|
221
|
+
IUIAutomationElement* child = nullptr;
|
|
222
|
+
if (FAILED(walker->GetFirstChildElement(parent, &child)) || child == nullptr) return;
|
|
223
|
+
while (child != nullptr) {
|
|
224
|
+
++visited;
|
|
225
|
+
if (visited >= kMaxVisited) truncated = true;
|
|
226
|
+
if (matches(child, req, queryWide, queryControlType)) {
|
|
227
|
+
Match m;
|
|
228
|
+
if (captureMatch(child, m)) matchesOut.push_back(m);
|
|
229
|
+
}
|
|
230
|
+
if (!truncated && static_cast<int>(matchesOut.size()) < req.maxResults) {
|
|
231
|
+
walk(walker, child, req, queryWide, queryControlType, depth + 1, visited, truncated, matchesOut);
|
|
232
|
+
}
|
|
233
|
+
IUIAutomationElement* next = nullptr;
|
|
234
|
+
walker->GetNextSiblingElement(child, &next);
|
|
235
|
+
child->Release();
|
|
236
|
+
child = next;
|
|
237
|
+
if (truncated || static_cast<int>(matchesOut.size()) >= req.maxResults) {
|
|
238
|
+
if (child) child->Release();
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static unsigned long long hwndNumber(HWND h) {
|
|
245
|
+
return static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(h));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static int runSelftest() {
|
|
249
|
+
if (controlTypeFromQuery("button") != UIA_ButtonControlTypeId) return 2;
|
|
250
|
+
std::printf("{\"schema\":\"deadbyte.desktop-uia-selftest.v1\",\"status\":\"passed\"}\n");
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
int wmain(int argc, wchar_t** argv) {
|
|
255
|
+
if (argc == 2 && std::wstring(argv[1]) == L"--selftest") return runSelftest();
|
|
256
|
+
const Request req = readRequest();
|
|
257
|
+
|
|
258
|
+
HWND fg = GetForegroundWindow();
|
|
259
|
+
DWORD pid = 0;
|
|
260
|
+
if (fg) GetWindowThreadProcessId(fg, &pid);
|
|
261
|
+
if (fg != req.expectedHwnd || pid != req.expectedPid) fail("foreground window identity changed", 3);
|
|
262
|
+
|
|
263
|
+
const HRESULT init = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
264
|
+
if (FAILED(init) && init != RPC_E_CHANGED_MODE) fail("CoInitializeEx failed", 4);
|
|
265
|
+
const bool uninit = SUCCEEDED(init);
|
|
266
|
+
|
|
267
|
+
IUIAutomation* automation = nullptr;
|
|
268
|
+
HRESULT hr = CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
|
|
269
|
+
if (FAILED(hr) || !automation) {
|
|
270
|
+
if (uninit) CoUninitialize();
|
|
271
|
+
fail("CUIAutomation creation failed", 5);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
IUIAutomationElement* root = nullptr;
|
|
275
|
+
hr = automation->ElementFromHandle(fg, &root);
|
|
276
|
+
if (FAILED(hr) || !root) {
|
|
277
|
+
automation->Release(); if (uninit) CoUninitialize();
|
|
278
|
+
fail("ElementFromHandle failed", 6);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
std::vector<Match> found;
|
|
282
|
+
int visited = 0;
|
|
283
|
+
bool truncated = false;
|
|
284
|
+
const std::wstring queryWide = utf8ToWide(req.queryUtf8);
|
|
285
|
+
const int queryControlType = req.kind == "control_type" ? controlTypeFromQuery(req.queryUtf8) : 0;
|
|
286
|
+
if (req.kind == "control_type" && queryControlType == 0) {
|
|
287
|
+
root->Release(); automation->Release(); if (uninit) CoUninitialize();
|
|
288
|
+
fail("unsupported control_type query", 7);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (req.kind == "root") {
|
|
292
|
+
++visited;
|
|
293
|
+
RECT wr{};
|
|
294
|
+
if (GetWindowRect(fg, &wr)) {
|
|
295
|
+
const LONG vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
296
|
+
const LONG vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
297
|
+
const LONG vr = vx + GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
298
|
+
const LONG vb = vy + GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
299
|
+
const LONG left = (std::max)(wr.left, vx);
|
|
300
|
+
const LONG top = (std::max)(wr.top, vy);
|
|
301
|
+
const LONG right = (std::min)(wr.right, vr);
|
|
302
|
+
const LONG bottom = (std::min)(wr.bottom, vb);
|
|
303
|
+
if (right > left && bottom > top && IsWindowVisible(fg)) {
|
|
304
|
+
Match m;
|
|
305
|
+
m.left = left; m.top = top; m.width = right - left; m.height = bottom - top;
|
|
306
|
+
m.controlType = UIA_WindowControlTypeId;
|
|
307
|
+
m.enabled = IsWindowEnabled(fg) != FALSE;
|
|
308
|
+
m.offscreen = false;
|
|
309
|
+
found.push_back(m);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
IUIAutomationTreeWalker* walker = nullptr;
|
|
314
|
+
hr = automation->get_ControlViewWalker(&walker);
|
|
315
|
+
if (FAILED(hr) || !walker) {
|
|
316
|
+
root->Release(); automation->Release(); if (uninit) CoUninitialize();
|
|
317
|
+
fail("ControlViewWalker unavailable", 8);
|
|
318
|
+
}
|
|
319
|
+
walk(walker, root, req, queryWide, queryControlType, 0, visited, truncated, found);
|
|
320
|
+
walker->Release();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
std::printf("{\"schema\":\"deadbyte.desktop-uia-ground.v1\",\"foreground_hwnd\":\"0x%llx\",\"foreground_pid\":%lu,"
|
|
324
|
+
"\"query_kind\":\"%s\",\"visited\":%d,\"truncated\":%s,\"matches\":[",
|
|
325
|
+
hwndNumber(fg), static_cast<unsigned long>(pid), req.kind.c_str(), visited, truncated ? "true" : "false");
|
|
326
|
+
for (size_t i = 0; i < found.size(); ++i) {
|
|
327
|
+
if (i) std::printf(",");
|
|
328
|
+
const auto& m = found[i];
|
|
329
|
+
std::printf("{\"left\":%ld,\"top\":%ld,\"width\":%ld,\"height\":%ld,\"control_type\":%d,"
|
|
330
|
+
"\"enabled\":%s,\"offscreen\":%s,\"runtime_id\":[",
|
|
331
|
+
m.left, m.top, m.width, m.height, m.controlType,
|
|
332
|
+
m.enabled ? "true" : "false", m.offscreen ? "true" : "false");
|
|
333
|
+
for (size_t j = 0; j < m.runtimeId.size(); ++j) {
|
|
334
|
+
if (j) std::printf(",");
|
|
335
|
+
std::printf("%ld", m.runtimeId[j]);
|
|
336
|
+
}
|
|
337
|
+
std::printf("]}");
|
|
338
|
+
}
|
|
339
|
+
std::printf("]}\n");
|
|
340
|
+
|
|
341
|
+
root->Release();
|
|
342
|
+
automation->Release();
|
|
343
|
+
if (uninit) CoUninitialize();
|
|
344
|
+
return 0;
|
|
345
|
+
}
|