@whyvix/remote 0.3.9 → 0.3.10

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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "artifacts": {
3
+ "win32-x64": {
4
+ "path": "native/win32-x64/whyvix-gui-window-action-helper.exe",
5
+ "sha256": "f1ee7726d60fc72a9826690e1d7072d4f0a8063b8db4bd1de52eb17d481d312c"
6
+ }
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whyvix/remote",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
@@ -10,16 +10,19 @@
10
10
  "packages/protocol/src",
11
11
  "native/gui-helper-manifest.json",
12
12
  "native/win32-x64/whyvix-gui-helper.exe",
13
+ "native/gui-window-action-helper-manifest.json",
14
+ "native/win32-x64/whyvix-gui-window-action-helper.exe",
13
15
  "README.md"
14
16
  ],
15
17
  "scripts": {
16
18
  "gateway": "node services/gateway/src/index.js",
17
19
  "agent": "node packages/agent/src/index.js",
18
20
  "test:r0": "node scripts/r0-test.js",
19
- "test": "npm run test:r0 && npm run test:r1-r2 && npm run test:r3-r5",
21
+ "test": "npm run test:r0 && npm run test:r1-r2 && npm run test:r3-r5 && npm run test:r10-gui-window-focus",
20
22
  "check": "node --check packages/protocol/src/index.js && node --check packages/agent/src/security.js && node --check packages/agent/src/process-manager.js && node --check packages/agent/src/tools.js && node --check packages/agent/src/agent.js && node --check packages/agent/src/index.js && node --check packages/cli/index.js && node --check services/gateway/src/device-registry.js && node --check services/gateway/src/gateway.js && node --check services/gateway/src/index.js && node --check scripts/r0-test.js && node --check scripts/r1-r2-test.js && node --check scripts/r3-r5-test.js",
21
23
  "test:r1-r2": "node scripts/r1-r2-test.js",
22
- "test:r3-r5": "node scripts/r3-r5-test.js"
24
+ "test:r3-r5": "node scripts/r3-r5-test.js",
25
+ "test:r10-gui-window-focus": "node scripts/r10-gui-window-action-contract-test.js && node scripts/r10-gui-window-action-source-test.js && node scripts/r10-gui-window-action-agent-capability-test.js && node scripts/r10-gui-window-action-authorization-test.js && node scripts/r10-gui-window-action-loader-test.js && node scripts/r10-gui-window-action-mcp-schema-test.js"
23
26
  },
24
27
  "dependencies": {
25
28
  "@modelcontextprotocol/ext-apps": "^2.0.0",
@@ -7,17 +7,19 @@ import { ProcessManager } from "./process-manager.js";
7
7
  import { RemoteExecutionManager } from "./remote-execution-manager.js";
8
8
  import { DeploymentManager } from "./deployment-manager.js";
9
9
  import { GuiManager } from "./gui-manager.js";
10
+ import { GuiWindowActionManager } from "./gui-window-action-manager.js";
10
11
  import { createToolExecutor } from "./tools.js";
11
12
 
12
- export function createAgent({gatewayUrl="ws://127.0.0.1:8787",deviceId=randomUUID(),deviceName=os.hostname(),token="",workspaces=[process.cwd()],remoteTargets=[],deploymentProfiles=[],deploymentTransferArtifact=null,guiBackend=null,agentVersion="unknown",capabilities=[],reconnectMs=1000,reconnect=true,heartbeatMs=30000,audit=()=>{},log=console.log,errorLog=console.error,onRegistered=()=>{}}={}) {
13
+ export function createAgent({gatewayUrl="ws://127.0.0.1:8787",deviceId=randomUUID(),deviceName=os.hostname(),token="",workspaces=[process.cwd()],remoteTargets=[],deploymentProfiles=[],deploymentTransferArtifact=null,guiBackend=null,guiWindowActionBackend=null,agentVersion="unknown",capabilities=[],reconnectMs=1000,reconnect=true,heartbeatMs=30000,audit=()=>{},log=console.log,errorLog=console.error,onRegistered=()=>{}}={}) {
13
14
  let ws=null,reconnectTimer=null,stopped=false,generation=0;
14
15
  const security=createSecurity({token,workspaces,audit});
15
16
  const processManager=new ProcessManager({audit});
16
17
  const remoteExecutionManager=new RemoteExecutionManager({targets:remoteTargets,audit});
17
18
  const deploymentManager=new DeploymentManager({profiles:deploymentProfiles,security,audit,transferArtifact:deploymentTransferArtifact});
18
19
  const guiManager=new GuiManager({backend:guiBackend,audit});
19
- const advertisedCapabilities=[...new Set([...capabilities,...(guiManager.capabilities().available?["gui.observe"]:[])])].sort();
20
- const executeTool=createToolExecutor({security,processManager,remoteExecutionManager,deploymentManager,guiManager});
20
+ const guiWindowActionManager=new GuiWindowActionManager({backend:guiWindowActionBackend,audit});
21
+ const advertisedCapabilities=[...new Set([...capabilities,...(guiManager.capabilities().available?["gui.observe"]:[]),...(guiWindowActionManager.capabilities().available?["gui.window.focus"]:[])])].sort();
22
+ const executeTool=createToolExecutor({security,processManager,remoteExecutionManager,deploymentManager,guiManager,guiWindowActionManager});
21
23
  function scheduleReconnect(reason){
22
24
  if(stopped||!reconnect||reconnectTimer)return;
23
25
  log("[agent:reconnect-scheduled]",reason,reconnectMs);
@@ -0,0 +1,3 @@
1
+ import path from "node:path";import { access,readFile } from "node:fs/promises";import { createReadStream } from "node:fs";import { createHash } from "node:crypto";import { fileURLToPath } from "node:url";import { WindowsGuiWindowActionBackend } from "./windows-gui-window-action-backend.js";
2
+ async function sha256(file){const hash=createHash("sha256");for await(const chunk of createReadStream(file))hash.update(chunk);return hash.digest("hex");}
3
+ export async function loadGuiWindowActionBackend({platform=process.platform,arch=process.arch,runAction,packageRoot}={}){if(platform!=="win32")return null;const root=packageRoot??path.resolve(path.dirname(fileURLToPath(import.meta.url)),"..","..",".."),key=`${platform}-${arch}`,manifestPath=path.join(root,"native","gui-window-action-helper-manifest.json");let manifest;try{manifest=JSON.parse(await readFile(manifestPath,"utf8"));}catch{return null;}const entry=manifest?.artifacts?.[key];if(!entry||typeof entry.path!=="string"||!/^[a-f0-9]{64}$/.test(entry.sha256??""))return null;const helperPath=path.resolve(root,entry.path);if(!helperPath.startsWith(path.resolve(root)+path.sep))return null;try{await access(helperPath);}catch{return null;}if(await sha256(helperPath)!==entry.sha256||typeof runAction!=="function")return null;return new WindowsGuiWindowActionBackend({helperPath,runAction});}
@@ -0,0 +1,6 @@
1
+ function fail(message,code){throw Object.assign(new Error(message),{code});}
2
+ export class GuiWindowActionManager{
3
+ constructor({backend=null,audit=()=>{}}={}){this.backend=backend;this.audit=audit;}
4
+ capabilities(){return{available:Boolean(this.backend),focus:Boolean(this.backend?.focusWindow)};}
5
+ async focusWindow({windowId}={}){const id=String(windowId??"");if(!/^\d{1,20}$/.test(id))fail("Invalid GUI window id","GUI_WINDOW_INVALID");if(typeof this.backend?.focusWindow!=="function")fail("GUI window action is not available","GUI_WINDOW_ACTION_NOT_AVAILABLE");const result=await this.backend.focusWindow({windowId:id});this.audit({event:"gui.window.focus",windowId:id});return result;}
6
+ }
@@ -21,6 +21,7 @@ export function createToolExecutor({ security, processManager, remoteExecutionMa
21
21
  if(tool==="gui.windows")return guiManager.windows();
22
22
  if(tool==="gui.foreground")return guiManager.foreground();
23
23
  if(tool==="gui.capture")return guiManager.capture(args);
24
+ if(tool==="gui.window.focus"){if(!guiWindowActionManager)throw Object.assign(new Error("GUI window action manager is not configured"),{code:"GUI_WINDOW_ACTION_NOT_AVAILABLE"});return guiWindowActionManager.focusWindow(args);}
24
25
  if(tool==="fs.begin_binary_write")return binaryTransferManager.begin(args);
25
26
  if(tool==="fs.write_binary_session")return binaryTransferManager.write(args);
26
27
  if(tool==="fs.finalize_binary_write")return binaryTransferManager.finalize(args);
@@ -0,0 +1,6 @@
1
+ import path from "node:path";
2
+ function fail(message,code){throw Object.assign(new Error(message),{code});}
3
+ export class WindowsGuiWindowActionBackend{
4
+ constructor({helperPath,runAction}={}){if(typeof runAction!=="function"||typeof helperPath!=="string"||!helperPath.trim())fail("Windows GUI window action helper is not configured","GUI_WINDOW_ACTION_NOT_CONFIGURED");this.name="windows-native-window-action";this.helperPath=path.resolve(helperPath);this.runAction=runAction;}
5
+ async focusWindow({windowId}){const response=await this.runAction({helperPath:this.helperPath,windowId});if(!response||typeof response!=="object"||Array.isArray(response))fail("Windows GUI window action returned invalid payload","GUI_WINDOW_ACTION_PROTOCOL_ERROR");if(response.error)fail(String(response.error.message||"GUI window action rejected"),String(response.error.code||"GUI_WINDOW_ACTION_FAILED"));return response;}
6
+ }
@@ -0,0 +1,2 @@
1
+ import { execFile } from "node:child_process";
2
+ export function runWindowsGuiWindowAction({helperPath,windowId}){return new Promise((resolve,reject)=>execFile(helperPath,["focus",String(windowId)],{windowsHide:true,timeout:5000,maxBuffer:65536,encoding:"utf8"},(error,stdout)=>{if(error)return reject(Object.assign(new Error("Windows GUI window action failed"),{code:error.killed?"GUI_WINDOW_ACTION_TIMEOUT":"GUI_WINDOW_ACTION_FAILED"}));try{resolve(JSON.parse(stdout));}catch{reject(Object.assign(new Error("Windows GUI window action returned invalid JSON"),{code:"GUI_WINDOW_ACTION_PROTOCOL_ERROR"}));}}));}
@@ -8,7 +8,9 @@ import { createAgent } from "../agent/src/agent.js";
8
8
  import { loadDeploymentProfiles } from "../agent/src/deployment-profile-loader.js";
9
9
  import { transferDeploymentArtifact } from "../agent/src/deployment-transfer-runner.js";
10
10
  import { loadGuiBackend } from "../agent/src/gui-backend-loader.js";
11
- import { runWindowsGuiHelper } from "../agent/src/windows-gui-helper-runner.js";
11
+ import { runWindowsGuiHelper } from "../agent/src/windows-gui-helper-runner.js";
12
+ import { loadGuiWindowActionBackend } from "../agent/src/gui-window-action-backend-loader.js";
13
+ import { runWindowsGuiWindowAction } from "../agent/src/windows-gui-window-action-runner.js";
12
14
  import { openBrowser } from "./browser.js";
13
15
 
14
16
  const packageRoot=join(dirname(fileURLToPath(import.meta.url)),"..","..");
@@ -23,6 +25,7 @@ const configuredDeploymentProfiles=String(process.env.WHYVIX_DEPLOY_PROFILES_FIL
23
25
  const defaultDeploymentProfiles=join(os.homedir(),".whyvix-remote","deployment-profiles.json");
24
26
  const deploymentProfiles=await loadDeploymentProfiles(configuredDeploymentProfiles||defaultDeploymentProfiles,{optional:!configuredDeploymentProfiles});
25
27
  const guiBackend=await loadGuiBackend({packageRoot,runHelper:runWindowsGuiHelper});
28
+ const guiWindowActionBackend=await loadGuiWindowActionBackend({packageRoot,runAction:runWindowsGuiWindowAction});
26
29
  const capabilities=["filesystem.crud","process.bounded","git.read","git.mutate",...(remoteTargets.length?["remote.bounded"]:[]),...(deploymentProfiles.length?["deployment.profiled"]:[])];
27
30
  const deviceId=process.env.WHYVIX_DEVICE_ID||crypto.createHash("sha256").update(os.hostname()+"|"+os.userInfo().username).digest("hex").slice(0,24);
28
31
  const credentialDir=join(os.homedir(),".whyvix-remote");