← All source files

V18 / 8d896b8a86e5

Program.cs

Interfaces/Gui/LilithGui.Worker/Program.cs

Unchanged from the preceding version
CS
using System.Reflection;
using System.Runtime.Loader;
using System.Runtime.InteropServices;
using System.Text.Json;
using Lilith.Gui.Worker;

if(args.Length!=2)throw new ArgumentException("Worker requires a pinned backend directory and isolated state directory.");
string backend=Path.GetFullPath(args[0]),state=Path.GetFullPath(args[1]);
// Selenium is loaded from the selected release, while AppContext.BaseDirectory
// belongs to this reflection host. Point its native helper at that same release.
if(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SE_MANAGER_PATH")))
{
    string platform=OperatingSystem.IsWindows()?"win":OperatingSystem.IsMacOS()?"osx":
        RuntimeInformation.ProcessArchitecture==Architecture.Arm64?"linux-arm64":"linux-x64";
    string manager=Path.Combine(backend,"runtimes",platform,"native",OperatingSystem.IsWindows()?"selenium-manager.exe":"selenium-manager");
    if(File.Exists(manager))Environment.SetEnvironmentVariable("SE_MANAGER_PATH",manager);
}
var resolver=new AssemblyDependencyResolver(Path.Combine(backend,"LilithConsole.dll"));
AssemblyLoadContext.Default.Resolving+=(context,name)=>
{
    string? path=resolver.ResolveAssemblyToPath(name);path??=Path.Combine(backend,name.Name+".dll");
    return File.Exists(path)?context.LoadFromAssemblyPath(path):null;
};
AssemblyLoadContext.Default.ResolvingUnmanagedDll+=(assembly,name)=>
{string? path=resolver.ResolveUnmanagedDllToPath(name);return path is null?IntPtr.Zero:NativeLibrary.Load(path);};
var core=AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(backend,"Core.dll"));
var agent=AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(backend,"Agent.dll"));
var writeLock=new object();
void Emit(string kind,object? data){lock(writeLock)Console.WriteLine("@lilith-gui "+JsonSerializer.Serialize(new{kind,data}));}
await using var runtime=new ReleaseRuntime(core,agent,backend,state,Emit);
Task? active=null;CancellationTokenSource? cancel=null;
while(await Console.In.ReadLineAsync() is {} line)
{
    JsonElement request;
    try{request=JsonSerializer.Deserialize<JsonElement>(line);}catch(JsonException){continue;}
    string id=request.GetProperty("id").GetString()!,action=request.GetProperty("action").GetString()!;
    var payload=request.GetProperty("payload").Clone();
    if(action=="browser_result"){DesktopResearchBridge.Complete(payload);continue;}
    if(action=="cancel"){cancel?.Cancel();Emit("reply",new{id,result=new{message="Cancellation requested."}});continue;}
    if(action=="stop")
    {
        cancel?.Cancel();if(active is not null)try{await active;}catch(Exception){}
        Emit("reply",new{id,result=new{message="Stopped."}});break;
    }
    if(active is {IsCompleted:false}){Emit("reply",new{id,error="A conversation operation is active. Wait or cancel it."});continue;}
    cancel?.Dispose();cancel=new();var token=cancel.Token;
    active=Task.Run(async()=>
    {
        bool failed=false;
        try{var result=await runtime.DispatchAsync(action,payload,token);if(action is "boot" or "chat" or "command" or "scenario" or "approval_decide")await runtime.PublishApprovals(token);Emit("reply",new{id,result});}
        catch(Exception ex){failed=true;while(ex is TargetInvocationException {InnerException:{}} inner)ex=inner.InnerException!;Emit("error",new{text=ex.Message,detail=ex.ToString()});Emit("status",new{state="error",text="Stopped"});Emit("reply",new{id,error=ex.Message});}
        finally{if(!failed)Emit("status",new{state="idle",text="Ready"});}
    });
}
cancel?.Cancel();if(active is not null)try{await active;}catch(Exception){}